【MyBatis】MyBatis自動生成代碼之查詢爬坑記

前言

項目使用SSM框架搭建Web後臺服務,前臺後使用restful api,後臺使用MyBatisGenerator自動生成代碼,在前臺使用關鍵字進行查詢時,遇到了一些很寶貴的坑,現記錄以下。爲展現所遇問題,將項目進行了精簡。java

項目框架

後臺框架

後臺框架選型爲Spring + SpringMVC + Mybatis + Jetty,其中使用MyBatisGenerator建立代碼,Jetty爲內嵌的Web服務器。mysql

項目代碼

代碼已上傳至githubgit

項目介紹

數據準備

建立庫ssm和表users,其中建立表usersSQL以下。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&amp;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

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服務,只作演示,其中參數並未進行調優處理。數據庫

Controller

只存在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

Service

UserServiceImpUserService的實現類。

  1. 根據用戶名查詢指定記錄

其源碼以下。

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"
    }]
}
  1. 根據用戶名和關鍵字查詢指定記錄

其源碼以下

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.javaCriteria以下。

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自動生成代碼時,須要注意以下可能出現的坑。

  • 當進行(a or b) and c查詢時,可經過轉變爲(a and c) or (b and c)方式進行查詢,但我的認爲更好的方法是修改Example文件,進行定製化的查詢處理。
  • 單獨使用andxxxLike時,不須要添加"%"處理,而配合or時,必須添加"%"才行。
相關文章
相關標籤/搜索