項目使用SSM框架搭建Web後臺服務,前臺後使用
restful api
,後臺使用MyBatisGenerator自動生成代碼,在前臺使用關鍵字進行查詢時,遇到了一些很寶貴的坑,現記錄以下。爲展現所遇問題,將項目進行了精簡。java
後臺框架選型爲Spring + SpringMVC + Mybatis + Jetty
,其中使用MyBatisGenerator
建立代碼,Jetty
爲內嵌的Web服務器。mysql
代碼已上傳至githubgit
建立庫ssm
和表users
,其中建立表users
的SQL
以下。github
CREATE TABLE `users` ( `id` int(10) NOT NULL AUTO_INCREMENT, `name` varchar(32) DEFAULT NULL, `address` varchar(32) DEFAULT NULL, `hobby` varchar(64) DEFAULT NULL, `content` text, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1 insert into users(name, address, hobby, content) values("leesf", "hubei", "sport, race", "he is a boy"); insert into users(name, address, hobby, content) values("dyd", "hubei", "painting, reading", "she is a girl");
使用MyBatisGenerator自動生成相應代碼,其源碼以下。web
package com.leesf; import org.junit.Test; import org.mybatis.generator.api.MyBatisGenerator; import org.mybatis.generator.config.Configuration; import org.mybatis.generator.config.xml.ConfigurationParser; import org.mybatis.generator.internal.DefaultShellCallback; import java.io.File; import java.util.ArrayList; import java.util.List; public class MybatisGenerator { @Test public void generator() throws Exception { List<String> warnings = new ArrayList<String>(); File configFile = new File( "F:/01_Code/01_Idea/ssm-master/src/test/generatorConfig.xml"); ConfigurationParser cp = new ConfigurationParser(warnings); Configuration config = cp.parseConfiguration(configFile); DefaultShellCallback callback = new DefaultShellCallback(true); MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config, callback, warnings); myBatisGenerator.generate(null); } }
其中generatorConfig.xml文件以下。spring
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE generatorConfiguration PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN" "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd"> <generatorConfiguration> <!-- <context> 元素用於指定生成一組對象的環境。 子元素用於指定要鏈接到的數據庫、 要生成對象的類型和要內省的表 --> <context id="testTables" targetRuntime="MyBatis3"> <commentGenerator> <!-- 是否去除自動生成的註釋 true:是 : false:否 --> <property name="suppressAllComments" value="true"/> </commentGenerator> <!--數據庫鏈接的信息:驅動類、鏈接地址、用戶名、密碼 --> <jdbcConnection driverClass="com.mysql.jdbc.Driver" connectionURL="jdbc:mysql://localhost:3306/ssm?useUnicode=true&characterEncoding=UTF-8" userId="root" password=""> </jdbcConnection> <!-- 默認false,把JDBC DECIMAL 和 NUMERIC 類型解析爲 Integer,爲 true時把JDBC DECIMAL 和 NUMERIC 類型解析爲java.math.BigDecimal --> <javaTypeResolver> <property name="forceBigDecimals" value="false"/> </javaTypeResolver> <!-- targetProject:生成PO類的位置 注意對於targetProject:In other environments(other than Eclipse), this value should be an existing directory on the local file system. 即對於非eclipse項目須要指定絕對路徑 --> <javaModelGenerator targetPackage="com.leesf.po" targetProject="F:/01_Code/01_Idea/ssm-master/src/main/java"> <!-- enableSubPackages:是否讓schema做爲包的後綴 --> <property name="enableSubPackages" value="false"/> <!-- 從數據庫返回的值被清理先後的空格 --> <property name="trimStrings" value="true"/> </javaModelGenerator> <!-- targetProject:mapper映射文件生成的位置 --> <sqlMapGenerator targetPackage="com.leesf.mapper" targetProject="F:/01_Code/01_Idea/ssm-master/src/main/java"> <!-- enableSubPackages:是否讓schema做爲包的後綴 --> <property name="enableSubPackages" value="false"/> </sqlMapGenerator> <!-- targetPackage:mapper接口生成的位置 --> <javaClientGenerator type="XMLMAPPER" targetPackage="com.leesf.mapper" targetProject="F:/01_Code/01_Idea/ssm-master/src/main/java"> <!-- enableSubPackages:是否讓schema做爲包的後綴 --> <property name="enableSubPackages" value="false"/> </javaClientGenerator> <!-- 指定數據庫表 --> <table tableName="users"></table> <!-- <table schema="" tableName="sys_user"></table> <table schema="" tableName="sys_role"></table> <table schema="" tableName="sys_permission"></table> <table schema="" tableName="sys_user_role"></table> <table schema="" tableName="sys_role_permission"></table> --> <!-- 有些表的字段須要指定java類型 <table schema="" tableName=""> <columnOverride column="" javaType="" /> </table> --> </context> </generatorConfiguration>
WebServer
爲Web容器,其源碼以下。sql
package com.leesf.main; import java.net.UnknownHostException; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.TimeUnit; import org.apache.commons.lang3.StringUtils; import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.nio.SelectChannelConnector; import org.eclipse.jetty.util.thread.ExecutorThreadPool; import org.eclipse.jetty.webapp.WebAppContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class WebServer { public static final String CONTEXT = "/"; private static final Logger LOG = LoggerFactory.getLogger(WebServer.class); private static final String DEFAULT_WEBAPP_PATH = "webapps/"; private Server server; private int port; public WebServer() { } public Server createServerInSource() throws UnknownHostException { port = 8081; server = new Server(); server.setStopAtShutdown(true); SelectChannelConnector connector = new SelectChannelConnector(); connector.setPort(port); connector.setReuseAddress(false); connector.setAcceptQueueSize(50); connector.setAcceptors(2); connector.setThreadPool( new ExecutorThreadPool(20, 40, 0, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>( 16, false))); connector.setLowResourcesMaxIdleTime(3000); connector.setReuseAddress(true); connector.setRequestBufferSize( 16 * 1024); connector.setRequestHeaderSize( 8 * 1024); server.setConnectors(new Connector[] { connector }); String basePath = "src/main/webapps"; if (StringUtils.isEmpty(basePath)) { basePath = DEFAULT_WEBAPP_PATH; } WebAppContext webContext = new WebAppContext(basePath, CONTEXT); webContext.setContextPath(CONTEXT); webContext.setDescriptor(basePath + "/WEB-INF/web.xml"); System.out.println("-------------web.xml path is " + webContext.getDescriptor() + "--------------"); webContext.setResourceBase(basePath); webContext.setClassLoader(Thread.currentThread().getContextClassLoader()); server.setHandler(webContext); return server; } public void start() throws Exception { if (server == null) { createServerInSource(); } if (server != null) { server.start(); LOG.info("WebServer has started at port:" + port); } } public void stop() throws Exception { if (server != null) { server.stop(); } } public static void main(String[] args) throws Exception { WebServer webServer = new WebServer(); webServer.start(); } }
使用內嵌Jetty
方式提供Web服務,只作演示,其中參數並未進行調優處理。數據庫
只存在UserController
,其源碼以下。apache
package com.leesf.controller; import com.leesf.po.Users; import com.leesf.service.UserService; import com.leesf.utils.ResultUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.OutputStreamWriter; import java.util.List; @Controller @RequestMapping(value = "/users") public class UserController { @Autowired UserService userService; @ResponseBody @RequestMapping(value = "/listUsers", method = { RequestMethod.POST, RequestMethod.GET }) public void listUsers( HttpServletRequest request, HttpServletResponse response, @RequestParam(required = false) String name, @RequestParam(required = false) String key) throws Exception { System.out.println("xxxxxx"); OutputStreamWriter out = new OutputStreamWriter(response.getOutputStream()); List<Users> users = userService.getUsers(name, key); ResultUtils.resultSuccess(users, out); } }
能夠根據用戶名字和關鍵字查詢用戶。json
UserServiceImp
爲UserService
的實現類。
其源碼以下。
package com.leesf.service.impl; import com.leesf.mapper.UsersMapper; import com.leesf.po.Users; import com.leesf.po.UsersExample; import com.leesf.service.UserService; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.HashMap; import java.util.List; import java.util.Map; @Service("userService") public class UserServiceImp implements UserService { @Autowired UsersMapper usersMapper; private Logger LOG = LoggerFactory.getLogger(this.getClass()); public List<Users> getUsers(String name, String key) { UsersExample usersExample = new UsersExample(); usersExample.createCriteria().andNameLike(name); List<Users> users = usersMapper.selectByExampleWithBLOBs(usersExample); return users; } }
能夠看到,該Service
根據name
查找匹配name
的記錄,啓動WebServer
,訪問http://localhost:8081/users/listUsers?name=sport
,查看編譯器運行的信息,發現構造了以下SQL語句,
select id, name, address, hobby , content from users WHERE ( name like ? )
。
url訪問結果以下:
{ result_code: "0", result_msg: "Succeed!", result_content: [{ id: 1, name: "leesf", address: "hubei", hobby: "sport, race", content: "he is a boy" }] }
其源碼以下
package com.leesf.service.impl; import com.leesf.mapper.UsersMapper; import com.leesf.po.Users; import com.leesf.po.UsersExample; import com.leesf.service.UserService; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.HashMap; import java.util.List; import java.util.Map; @Service("userService") public class UserServiceImp implements UserService { @Autowired UsersMapper usersMapper; private Logger LOG = LoggerFactory.getLogger(this.getClass()); public List<Users> getUsers(String name, String key) { UsersExample usersExample = new UsersExample(); if (StringUtils.isNotEmpty(key)) { usersExample.or().andAddressLike(key); usersExample.or().andHobbyLike(key); } if (StringUtils.isNotEmpty(name)) { if (usersExample.getOredCriteria().size() == 0){ usersExample.createCriteria(); } usersExample.getOredCriteria().get(0).andNameEqualTo(name); } List<Users> users = usersMapper.selectByExampleWithBLOBs(usersExample); return users; } }
key
能夠匹配address
或者hobby
,而name
必需要匹配name,也就是但願構造這樣的一條查詢SQLselect * from users where (address like "%sport%" or hobby like "%sport%") and name = "leesf"
。
啓動WebServer
,訪問http://localhost:8081/users/listUsers?key=sport&name=leesf
。
結果以下
{ result_code: "0", result_msg: "Succeed!", result_content: [] }
能夠看到訪問結果中result_content
爲空,查看運行日誌,發現以下SQL語句
select id, name, address, hobby , content from users WHERE ( address like ? and name = ? ) or( hobby like ? )
程序實際構造的SQL並不是以前所想的那樣,此時查閱資料,發現MyBatis自動生成代碼還不支持純生的(a or b) and c
這樣的SQL
語句,須要進行等價變化,即(a or b) and c = (a and c) or (b and c)
,具體可查看以下連接,按照這樣的思路進行以下修改。
public List<Users> getUsers(String name, String key) { UsersExample usersExample = new UsersExample(); if (StringUtils.isNotEmpty(key)) { usersExample.or().andAddressLike(key); usersExample.or().andHobbyLike(key); } if (StringUtils.isNotEmpty(name)){ if (usersExample.getOredCriteria().size() == 0){ usersExample.createCriteria(); } usersExample.getOredCriteria().get(0).andNameEqualTo(name); usersExample.getOredCriteria().get(1).andNameEqualTo(name); } List<Users> users = usersMapper.selectByExampleWithBLOBs(usersExample); return users; }
此時,再次查詢,發現仍是沒有結果,查看運行時信息發現以下SQL
。
select id, name, address, hobby , content from users WHERE ( address like ? and name = ? ) or( hobby like ? and name = ? )
,看似SQL
語句沒有任何問題,可是就是出不來結果,like
和前面也是同樣的,百思不得其解,繼續查閱資料也無解,後面嘗試對like
添加%
處理,修改以下。
public List<Users> getUsers(String name, String key) { UsersExample usersExample = new UsersExample(); if (StringUtils.isNotEmpty(key)) { usersExample.or().andAddressLike("%" + key + "%"); usersExample.or().andHobbyLike("%" + key + "%"); } if (StringUtils.isNotEmpty(name)) { if (usersExample.getOredCriteria().size() == 0){ usersExample.createCriteria(); } usersExample.getOredCriteria().get(0).andNameEqualTo(name); usersExample.getOredCriteria().get(1).andNameEqualTo(name); } List<Users> users = usersMapper.selectByExampleWithBLOBs(usersExample); return users; }
從新運行並訪問url,獲得以下結果:
{ result_code: "0", result_msg: "Succeed!", result_content: [{ id: 1, name: "leesf", address: "hubei", hobby: "sport, race", content: "he is a boy" }] }
在配合or使用的狀況下,like必須顯示添加%才能生效,雖然按照這種等價的方式能夠進行處理,仍是有些麻煩,特別是當or字段很是多的時候,處理比較麻煩,如(a or b or c or d) and e
,其須要處理成(a and e) or (b and e) or (c and e) or (d and e)
,繼續查閱資料,看是否有更爲簡便的寫法,在stackoverflow上發現有這樣的處理方式,進行以下改造。
public List<Users> getUsers(String name, String key) { UsersExample usersExample = new UsersExample(); if (StringUtils.isNotEmpty(key)) { Map<String, String> maps = new HashMap<String, String>(); maps.put("address", key); maps.put("hobby", key); usersExample.createCriteria().multiColumnOrLike(maps); } if (StringUtils.isNotEmpty(name)) { if (usersExample.getOredCriteria().size() == 0){ usersExample.createCriteria(); } usersExample.getOredCriteria().get(0).andNameEqualTo(name); } List<Users> users = usersMapper.selectByExampleWithBLOBs(usersExample); return users; }
修改UserExample.java
的Criteria
以下。
public static class Criteria extends GeneratedCriteria { protected Criteria() { super(); } public Criteria multiColumnOrLike(Map<String, String> maps) { StringBuffer stringBuffer = new StringBuffer(); stringBuffer.append("( "); for (Map.Entry<String, String> entry : maps.entrySet()) { stringBuffer.append(entry.getKey()); stringBuffer.append(" like "); stringBuffer.append("\"%"); stringBuffer.append(entry.getValue()); stringBuffer.append("%\""); stringBuffer.append(" or "); } int index = stringBuffer.lastIndexOf("or"); stringBuffer.delete(index, stringBuffer.length()); stringBuffer.append(")"); addCriterion(stringBuffer.toString()); return this; } }
再次啓動運行,結果以下。
{ result_code: "0", result_msg: "Succeed!", result_content: [{ id: 1, name: "leesf", address: "hubei", hobby: "sport, race", content: "he is a boy" }] }
能夠看到使用MyBatisGenerator自動生成代碼時,須要注意以下可能出現的坑。
or
時,必須添加"%"才行。