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元素调用存储过程。调用存储过程的方式为 :
<!-- 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 对象