Mybatis扩展

Posted OverZeal

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Mybatis扩展相关的知识,希望对你有一定的参考价值。

分页插件PageHelper

其实Mybstis内部有实现逻辑分页的功能,但是较为麻烦和难用。这里记录一个分页插件PageHelper的使用,我们可以在它的github地址https://github.com/pagehelper/Mybatis-PageHelper/blob/master/wikis/zh/HowToUse.md 中看到使用说明。

步骤一:引入jar包,我们使用Maven引入即可

        <!-- PageHelper分页工具 -->
        <!-- https://mvnrepository.com/artifact/com.github.pagehelper/pagehelper -->
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper</artifactId>
            <version>5.1.1</version>
        </dependency>

步骤二:配置拦截器插件,在Mybatis的全局配置文件的plugins元素中配置这个插件

  <!-- 配置PageHelper -->
      <plugins>
        <plugin interceptor="com.github.pagehelper.PageInterceptor">
        <!-- 可以配置让分页参数合理化(就是不会出现负数和大于最大页数的情况) -->
          <property name="reasonable" value="true"/>
        </plugin>
    </plugins>

我在这里配置了一个resonable属性,并将其值设置为true,这是为了使分页的页码数值合理化,不能出现不合理的页码(复数或大于最大页码的数值出现)

还有许多其他的属性,具体的看使用说明中有详细的参数说明。

步骤三:在代码中使用

        //引入PageHelper分页插件
        //传入当前页和每页记录数
        PageHelper.startPage(pn, 5);
        
        //startPage后面的查询就变成了分页查询
        List<Employee> list=emplService.getAllEmps();

之前我们知道了插件使用其实是拦截器,插件都会拦截四大对象,所以会对我们的操作进行修改,所以它后面的查询结果都会自动完成(根据我们传入的值)分页

PageHelper.startPage 的两个参数,第一个表示的是当前页码(就是在界面上显示的页码数,从1开始,而不是数据库中记录的从0开始);第二个参数是每页记录数(需要查几条数据)

Ok,返回给前端的数据就是分页后的数据,如果我们还需要得到一些如:当前页,是否为第一页,是否为最后一页,有没有下一页之类的信息,使用PageInfo对象即可

            EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
            Page<Object> page = PageHelper.startPage(5, 1);
            
            List<Employee> emps = mapper.getEmps();
            //传入要连续显示多少页
            PageInfo<Employee> info = new PageInfo<>(emps);
            for (Employee employee : emps) {
                System.out.println(employee);
            }
            System.out.println("当前页码:"+info.getPageNum());
            System.out.println("总记录数:"+info.getTotal());
            System.out.println("每页的记录数:"+info.getPageSize());
            System.out.println("总页码:"+info.getPages());
            System.out.println("是否第一页:"+info.isIsFirstPage());

还有很多的分页信息都可以从PageInfo对象中获得,具体的查看文档就行了

其实 PageHelper.startPage 会返回一个Page对象,这个Page对象也有一些分页信息,但是不如PageInfo的详细

在初始化PageInfo的时候,可以传入第二个参数表示要连续显示多少页

PageInfo<Employee> info = new PageInfo<>(emps, 5);
System.out.println("连续显示的页码:");
            int[] nums = info.getNavigatepageNums();
            for (int i = 0; i < nums.length; i++) {
                System.out.println(nums[i]);
            }

连续显示多少页就是说  当前是5  那么1,2,3页的时候,页码显示的都是12345 ;到第4页时,页码显示的就是23456。页码始终保持5页

如果我们后台不需要处理页码这些分页信息,但是还需要这些信息供前端处理,那么我们直接返回这个PageInfo对象就行

@RequestMapping("/getAllEmps.action")
@ResponseBody
public PageInfo getAllEmpsWithJson(@RequestParam(value="pn",defaultValue="1") Integer pn){
  //引入PageHelper分页插件
  //传入当前页和每页记录数
  PageHelper.startPage(pn, 5);
  
//startPage后面的查询就变成了分页查询 List<Employee> list=emplService.getAllEmps(); //将结果给PageInfo,并将PageInfo交给页面即可 //PageInfo封装了查出来的数据和页码数据 //这个构造器表示连续显示5页 PageInfo pageInfo=new PageInfo(list, 5);
return pageInfo; }

在页面上我们使用EL或AJax取出所需数据,我们再来简单看看这个PageInfo的内部属性

批处理

在多条插入语句或多条更新语句的时候,如果一条一条SQL去处理,数据库对于每一条SQL都要编译->设置参数->运行;使用批处理,数据库只把SQL编译一次(因为每个批处理的SQL结构都是一样的),只需要每次设置不同的参数。

所以当涉及到批量更新或批量插入的时候,使用批处理速度要快。在Mybatis中也是支持批处理的,之前使用动态SQL的时候我们用foreach元素拼过一条很长的SQL,但是因为数据库对SQL长度会有限制,所以太长的SQL语句是不能作为批处理的。Mybatis的配置文件settings中有一个defultExecutorType属性,它默认是SIMPLE(简单的执行器),我们可以改为BATCH(批处理执行器),但是这样的配置是全局配置,所有的执行器都会变为批处理执行器,所以不推荐使用。

当然了,我们可以在局部使用批处理,就是在openSession的时候给个参数

//可以执行批量操作的sqlSession
SqlSession openSession = sqlSessionFactory.openSession(ExecutorType.BATCH);

我们用这个SqlSession执行的SQL就是在批处理

    @Test
    public void testBatch() throws IOException{
        SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
        
        //可以执行批量操作的sqlSession
        SqlSession openSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
        long start = System.currentTimeMillis();
        try{
            EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
            for (int i = 0; i < 10000; i++) {
                mapper.addEmp(new Employee(UUID.randomUUID().toString().substring(0, 5), "b", "1"));
            }
            openSession.commit();
            long end = System.currentTimeMillis();
            //批量:(预编译sql一次==>设置参数===>10000次===>执行(1次))
            //Parameters: 616c1(String), b(String), 1(String)==>4598
            //非批量:(预编译sql=设置参数=执行)==》10000    10200
            System.out.println("执行时长:"+(end-start));
        }finally{
            openSession.close();
        }
        
    }

在我们Mybatis-Spring整合之后,我们就不直接使用SqlSession了,那么如何与Spring整合呢?

需要在Spring配置文件中配置SqlSessionTemplate 这个bean

<!--配置一个可以进行批量执行的sqlSession  -->
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
   <constructor-arg name="sqlSessionFactory" ref="sqlSessionFactoryBean"></constructor-arg>
   <constructor-arg name="executorType" value="BATCH"></constructor-arg>
</bean>

然后在Service层配置并注入SqlSession,使用这个SqlSession来操作,就像直接操作原生Mybatis操作一样:

使用批处理需要注意的问题:采用批处理的执行器,默认情况下只有在操作到session.commit()才会被Mybatis发送SQL到数据库执行,所以如果在批处理未提交之前还存在其他操作(例如=再查询这条记录)就会发生空指针异常发生回滚。

SqlSession sqlSession=sqlSessionFactory.openSession(ExecutorType.BATCH);
try{
        UserMapper userMapper=sqlSession.getMapper(UserMapper.class);
        User user=new User("lz",21);
        userMapper.insertUser(user);
        User user2=userMapper.getUser(1);
        System.err.println(user2.getUserName);
        sqlSession.commit();
}catch(Exception e{
        sqlSession.rollback();
        e.printStackTrace();
})finally{
        if(sqlSession!=null){
            sqlSession.close();
         }
}

System.err.println(user2.getUserName);  这里就会报空指向异常,因为我们的commit是在最后才提交的。而我们在getUser方法调用之前并不想提交事务,以为后面可能还会有其他的数据库操作在这个事务中,这个时候我们只需调用SqlSession的flushStatement方法便可以了,它的含义是将当前缓存的SQL发送给数据库执行。

userMapper.insertUser(user);
session.flushStatement();
User user2=userMapper.getUser(1);
System.err.println(user2.getUserName);
sqlSession.commit();

这样就避免了在批处理后面的操作错误,我们在使用批处理的时候要特别注意这一点。

调用存储过程

如果数据库中建有存储过程的话,我们Mybatis程序又该如何调用呢?

首先,我们默认使用的statementHandler是预编译的prepare,所以在处理存储过程的时候需要切换为 “CALLABLE” ,然后我们需要用select元素调用存储过程。调用存储过程的方式为 :

{call procedure_name(params)}
    <!-- public void getPageByProcedure(); 
    1、使用select标签定义调用存储过程
    2、statementType="CALLABLE":表示要调用存储过程
    3、{call procedure_name(params)}
    -->
<select id="getPageByProcedure" statementType="CALLABLE" databaseId="oracle">
    {call hello_test(
         #{start,mode=IN,jdbcType=INTEGER},
         #{end,mode=IN,jdbcType=INTEGER},
         #{count,mode=OUT,jdbcType=INTEGER},
         #{emps,mode=OUT,jdbcType=CURSOR,javaType=ResultSet,resultMap=PageEmp}
    )}
</select>

我们使用mode设置存储过程的入参(IN)和出参(OUT) ;最后一个emps是调用一个游标,游标的javaType对应的是ResultSet,使用自定义的结果集resultMap封装游标数据

MBG代码生成器

Mybatis官方提供了一个代码生成器用于自动生成JavaBean和Mapper, 地址在https://github.com/mybatis/generator 太详细的使用可以看这个地址的文档说明,这里记录一个简单的使用

首先在工程下导入所需jar包

然后我们在工程目录下建立一个MBG的配置文件,名为 generatorConfig.xml (这个文件存放的路径和名字不是固定的)

<?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 id="testTables" targetRuntime="MyBatis3">
        <commentGenerator>
            <!-- 是否去除自动生成的注释 true:是 false:否 -->
            <property name="suppressAllComments" value="true" />
        </commentGenerator>
        <!-- mysql数据库链接URL、用户名、密码 -->
         <jdbcConnection driverClass="com.mysql.jdbc.Driver"
            connectionURL="jdbc:mysql://localhost:3306/ssm_crud?characterEncoding=utf8"
            userId="root" password="root">
            
        </jdbcConnection>

        <!-- 默认是false,把JDBC DECIMAL和NUMERIC 类型解析为 Integer 设置为true表示把JDBC DECIMAL和NUMERIC 
            类型解析为 java.math.BigDecimal -->
        <javaTypeResolver>
            <property name="forceBigDecimals" value="false" />
        </javaTypeResolver>
        <!-- 生成pojo模型的包名和位置 -->
        <javaModelGenerator targetPackage="cn.lynu.model"
            targetProject=".\\src\\main\\java">
            <!-- enableSubPackages是否让schema作为包的后缀 -->
            <property name="enableSubPackages" value="false" />
            <!--trimStrings从数据库中返回的值将被清理前后空格 -->
            <property name="trimStrings" value="true" />
        </javaModelGenerator>
        <!-- 生成的mapper映射文件包名和位置 -->
        <sqlMapGenerator targetPackage="mapper"
            targetProject=".\\src\\main\\resources">
            <property name="enableSubPackages" value="false" />
        </sqlMapGenerator>
        <!-- 生成mapper接口的包名和位置 -->
        <javaClientGenerator type="XMLMAPPER"
            targetPackage="cn.lynu.mapper" targetProject=".\\src\\main\\java">
            <property name="enableSubPackages" value="false" />
        </javaClientGenerator>
        <!-- 指定数据库中的表 ,可以使用domainObjectName指定表生成的pojo类名-->
        <!--<table tableName="tbl_emp" domainObjectName="Employee"></table>-->        
        <table tableName="tb_emp" domainObjectName="Employee"/>
        <table tableName="tb_dept" domainObjectName="Department"/>
    </context>
</generatorConfiguration>  

这里使用了一个 targetRuntime 指定MBG的运行环境,这个值为 MyBatis3 表示会生成基本的CRUD和带查询条件的查询

最后我们使用一个java类来运行就会开始代码生成

    @Test
    public void test() throws Exception {
        List<String> warnings = new ArrayList<String>();
        boolean overwrite = true;
        // 指定generatorConfig.xml配置文件位置
        File configFile = new File("generatorConfig.xml");
        ConfigurationParser cp = new ConfigurationParser(warnings);
        Configuration config = cp.parseConfiguration(configFile);
        DefaultShellCallback callback = new DefaultShellCallback(overwrite);
        MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config, callback, warnings);
        myBatisGenerator.generate(null);
    }

使用QBC多条件查询

    @Test
    public void testMyBatis3() throws IOException{
        SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
        SqlSession openSession = sqlSessionFactory.openSession();
        try{
            EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
            //xxxExample就是封装查询条件的
            //1、查询所有
            //List<Employee> emps = mapper.selectByExample(null);
            //2、查询员工名字中有e字母的,和员工性别是1的
            //封装员工查询条件的example
            EmployeeExample example = new EmployeeExample();
            //创建一个Criteria,这个Criteria就是拼装查询条件
            //select id, last_name, email, gender, d_id from tbl_employee 
            //WHERE ( last_name like ? and gender = ? )
            Criteria criteria = example.createCriteria();
            //查询条件需要使用Criteria对象的and..()方法添加
            criteria.andLastNameLike("%e%");
            criteria.andGenderEqualTo("1");
            
            //select id, last_name, email, gender, d_id from tbl_employee 
            //WHERE ( last_name like ? and gender = ? ) or email like "%e%"
            //or条件需要新生成Criteria
            Criteria criteria2 = example.createCriteria();
            criteria2.andEmailLike("%e%");
            example.or(criteria2);
            
            List<Employee> list = mapper.selectByExample(example);
            for (Employee employee : list) {
                System.out.println(employee.getId());
            }
            
        }finally{
            openSession.close();
        }
    }

在这里Mybatis使用类似于Hibernate的QBC的方式使用 Criteria 完成多条件的查询,每个and条件都可以用一个Criteria的and...()方法添加条件,但是如果是or的条件就必须新生成一个 Cirteria来设置条件,并将这个新的Criteria添加进 Example 对象

 

以上是关于Mybatis扩展的主要内容,如果未能解决你的问题,请参考以下文章

mybatis学习(39):动态sql片段

SSM-MyBatis-05:Mybatis中别名,sql片段和模糊查询加getMapper

mybatis动态sql片段与分页,排序,传参的使用

MyBatis动态SQL标签用法

MYBATIS05_ifwherechoosewhentrimsetforEach标签sql片段

代码演示Mybatis-Generator 扩展自定义生成