mybatis-映射器-级联性能分析

来源:互联网 发布:python运维检程序脚本 编辑:程序博客网 时间:2024/06/14 22:01

       一:性能分析N+1问题

       级联的优势就是能够方便快捷的获取数据。比如学生和学生成绩信息往往是最常用的级联信息,这个时候级联完全是有必要的,级联最好不要超过三层,多层级联会导致复杂性的增加,不利于理解和维护。同时级联也存在严重的问题,例如我只对学生课程和成绩感兴趣,就不用取出学生证和健康情况表了。因为取出学生证和健康表不但没有意义而且还会多执行sql,导致性能下降。

     级联还有更严重的问题,假设表关联到Student表里面,那么可以想象,我们还要增加级联关系到这个结果集里,那么级联关系将会异常复杂。如果我们采取类似默认的场景那么有一个关联我们就要多执行一次sql,每次取出一个Student对象,那么它所有信息都会被取出来,这样造成sql执行过多,导致性能下降,这就是N+1问题,为了解决这个问题我们可以考虑延迟加载的功能。

     

        二:延迟加载

       为了处理N+1问题,mybatis引入了延迟加载的功能,延迟加载的意义在于一开始并不取出级联数据,只有当使用它了才发送sql去取回数据。

      mybatis的配置中有两个全局的参数lazyLoadingEnabled和aggressiveLazyLoading。lazyLoadingEnable的含义是是否开启延迟加载功能。aggressiveLazyLoading的含义是对任意延迟属性的调用会使带有延迟加载属性的对象完整加载;反之每种属性按需加载。


     mybatis默认情况都是及时加载的,现在修改mybatis配置问题settings元素里面的lazyLoadingEnabled值开启延迟加载,使得关联属性都按需加载,而不自动加载。

     

<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE configuration  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"  "http://mybatis.org/dtd/mybatis-3-config.dtd"><configuration><properties resource="org/mybatis/config/database.properties" /><settings><setting name="logImpl" value="LOG4J" /><setting name="lazyLoadingEnabled" value="true"/></settings><!-- 定义类的别名 --><typeAliases><typeAlias type="org.mybatis.pojo.Role" alias="role" /></typeAliases><!-- 定义数据库信息,默认使用development数据库构建环境 --><environments default="development"><environment id="development"><!--采用jdbc事务管理 --><transactionManager type="JDBC"><property name="autoCommit" value="false" /></transactionManager><!-- 配置数据库连接信息 --><dataSource type="POOLED"><property name="driver" value="${driver}" /><property name="url" value="${url}" /><property name="username" value="${username}" /><property name="password" value="${password}" /></dataSource></environment></environments><mappers><mapper resource="org/mybatis/mapper/RoleMapper.xml" /><mapper resource="org/mybatis/mapper/userMapper.xml" /><mapper resource="org/mybatis/mapper/StudentMapper.xml" /><mapper resource="org/mybatis/mapper/StudentCardMapper.xml" /><mapper resource="org/mybatis/mapper/StudentLectureMapper.xml" /><mapper resource="org/mybatis/mapper/LectureMapper.xml" /><mapper resource="org/mybatis/mapper/MaleStudentMapper.xml" /><mapper resource="org/mybatis/mapper/FemaleStudentMapper.xml" /></mappers></configuration>

     测试程序修改如下:

    

package org.mybatis.test;import org.apache.ibatis.session.SqlSession;import org.mybatis.mapper.StudentMapper;import org.mybatis.pojo.FemaleStudent;import org.mybatis.pojo.MaleStudent;import org.mybatis.pojo.Student;import org.mybatis.util.SqlSessionFactoryUtil;public class MybatisMainTest5 {public static void main(String[] args) {// TODO Auto-generated method stubSqlSession sqlSession = null;try {sqlSession = SqlSessionFactoryUtil.openSqlSession();StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);Student student = studentMapper.getStudent(1);// 打印学生对象System.out.println("the student is : " + student);// StudentLecture studentLecture =// student.getStudentLectureList().get(0);// 打印成绩信息// System.out.println("成绩信息:" + studentLecture);// System.out.println("打印课程信息:" + studentLecture.getLecture());// 判断是男性还是女性if (student instanceof MaleStudent) {MaleStudent maleStudent = (MaleStudent) student;System.out.println("打印男学生健康检查信息: " + maleStudent.getStudentHealthMaleList().get(0));} else {FemaleStudent maleStudent = (FemaleStudent) student;System.out.println("打印女学生健康检查信息: " + maleStudent.getStudentHealthFemaleList().get(0));}} catch (Exception e) {System.err.println(e.getMessage());sqlSession.rollback();} finally {if (sqlSession != null) {sqlSession.close();}}}}

      测试结果如下:

     

org.apache.ibatis.transaction.jdbc.JdbcTransaction:  Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@79be0360]DEBUG  2017-06-17 10:22:52,587  org.apache.ibatis.logging.jdbc.BaseJdbcLogger:  ==>  Preparing: select id,name,sex,card_no,note from t_student where id = ? DEBUG  2017-06-17 10:22:52,622  org.apache.ibatis.logging.jdbc.BaseJdbcLogger:  ==> Parameters: 1(Integer)DEBUG  2017-06-17 10:22:52,748  org.apache.ibatis.logging.jdbc.BaseJdbcLogger:  <==      Total: 1DEBUG  2017-06-17 10:22:52,748  org.apache.ibatis.logging.jdbc.BaseJdbcLogger:  ==>  Preparing: select id,student_id,native_place,issue_date,end_date,note from t_student_card where student_id =? DEBUG  2017-06-17 10:22:52,749  org.apache.ibatis.logging.jdbc.BaseJdbcLogger:  ==> Parameters: 1(Integer)DEBUG  2017-06-17 10:22:52,751  org.apache.ibatis.logging.jdbc.BaseJdbcLogger:  <==      Total: 1DEBUG  2017-06-17 10:22:52,751  org.apache.ibatis.logging.jdbc.BaseJdbcLogger:  ==>  Preparing: select id,student_id,lecture_id,grade,note from t_student_lecture where student_id = ? DEBUG  2017-06-17 10:22:52,751  org.apache.ibatis.logging.jdbc.BaseJdbcLogger:  ==> Parameters: 1(Integer)DEBUG  2017-06-17 10:22:52,757  org.apache.ibatis.logging.jdbc.BaseJdbcLogger:  <==      Total: 1DEBUG  2017-06-17 10:22:52,757  org.apache.ibatis.logging.jdbc.BaseJdbcLogger:  ==>  Preparing: select id,student_id studentId,check_date checkDate,heart, liver,spleen,lung,kidney,uterus,note from t_student_health_female where student_id = ? DEBUG  2017-06-17 10:22:52,757  org.apache.ibatis.logging.jdbc.BaseJdbcLogger:  ==> Parameters: 1(Integer)DEBUG  2017-06-17 10:22:52,801  org.apache.ibatis.logging.jdbc.BaseJdbcLogger:  <==      Total: 1the student is : FemaleStudent [studentHealthFemaleList=[StudentHealthFemale [id=1, studentId=1, checkDate=2017-0615, heart=好, liver=好, spleen=好, lung=好, kidney=好, uterus=好, note=1号女学生健康检查表]]]打印女学生健康检查信息: StudentHealthFemale [id=1, studentId=1, checkDate=2017-0615, heart=好, liver=好, spleen=好, lung=好, kidney=好, uterus=好, note=1号女学生健康检查表]DEBUG  2017-06-17 10:22:52,802  org.apache.ibatis.transaction.jdbc.JdbcTransaction:  Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@79be0360]DEBUG  2017-06-17 10:22:52,803  org.apache.ibatis.transaction.jdbc.JdbcTransaction:  Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@79be0360]DEBUG  2017-06-17 10:22:52,803  org.apache.ibatis.datasource.pooled.PooledDataSource:  Returned connection 2042495840 to pool.

     从上面的日志可以看出,只执行了学生的sql获取学生信息,执行学生健康检查表的sql获取检查信息,学生证和学生成绩的sql没有执行,也就没调用,说明延迟加载了。但是注意这个延迟加载是按mybatis的默认的按层级加载的。


     我们在修改测试代码,代码如下:

  

package org.mybatis.test;import org.apache.ibatis.session.SqlSession;import org.mybatis.mapper.StudentMapper;import org.mybatis.pojo.Student;import org.mybatis.util.SqlSessionFactoryUtil;public class MybatisMainTest5 {public static void main(String[] args) {// TODO Auto-generated method stubSqlSession sqlSession = null;try {sqlSession = SqlSessionFactoryUtil.openSqlSession();StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);Student student = studentMapper.getStudent(1);// 打印学生对象System.out.println("the student is : " + student);// StudentLecture studentLecture =// student.getStudentLectureList().get(0);// 打印成绩信息// System.out.println("成绩信息:" + studentLecture);// System.out.println("打印课程信息:" + studentLecture.getLecture());// 判断是男性还是女性/* * if (student instanceof MaleStudent) { MaleStudent maleStudent = * (MaleStudent) student; System.out.println("打印男学生健康检查信息: " + * maleStudent.getStudentHealthMaleList().get(0)); } else { * FemaleStudent maleStudent = (FemaleStudent) student; * System.out.println("打印女学生健康检查信息: " + * maleStudent.getStudentHealthFemaleList().get(0)); } */} catch (Exception e) {System.err.println(e.getMessage());sqlSession.rollback();} finally {if (sqlSession != null) {sqlSession.close();}}}}


      运行测试程序,日志如下:

   

DEBUG  2017-06-17 10:30:41,977  org.apache.ibatis.datasource.pooled.PooledDataSource:  Created connection 2042495840.DEBUG  2017-06-17 10:30:41,977  org.apache.ibatis.transaction.jdbc.JdbcTransaction:  Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@79be0360]DEBUG  2017-06-17 10:30:41,979  org.apache.ibatis.logging.jdbc.BaseJdbcLogger:  ==>  Preparing: select id,name,sex,card_no,note from t_student where id = ? DEBUG  2017-06-17 10:30:42,010  org.apache.ibatis.logging.jdbc.BaseJdbcLogger:  ==> Parameters: 1(Integer)DEBUG  2017-06-17 10:30:42,120  org.apache.ibatis.logging.jdbc.BaseJdbcLogger:  <==      Total: 1DEBUG  2017-06-17 10:30:42,122  org.apache.ibatis.logging.jdbc.BaseJdbcLogger:  ==>  Preparing: select id,student_id,native_place,issue_date,end_date,note from t_student_card where student_id =? DEBUG  2017-06-17 10:30:42,122  org.apache.ibatis.logging.jdbc.BaseJdbcLogger:  ==> Parameters: 1(Integer)DEBUG  2017-06-17 10:30:42,126  org.apache.ibatis.logging.jdbc.BaseJdbcLogger:  <==      Total: 1DEBUG  2017-06-17 10:30:42,126  org.apache.ibatis.logging.jdbc.BaseJdbcLogger:  ==>  Preparing: select id,student_id,lecture_id,grade,note from t_student_lecture where student_id = ? DEBUG  2017-06-17 10:30:42,126  org.apache.ibatis.logging.jdbc.BaseJdbcLogger:  ==> Parameters: 1(Integer)DEBUG  2017-06-17 10:30:42,130  org.apache.ibatis.logging.jdbc.BaseJdbcLogger:  <==      Total: 1DEBUG  2017-06-17 10:30:42,130  org.apache.ibatis.logging.jdbc.BaseJdbcLogger:  ==>  Preparing: select id,student_id studentId,check_date checkDate,heart, liver,spleen,lung,kidney,uterus,note from t_student_health_female where student_id = ? DEBUG  2017-06-17 10:30:42,131  org.apache.ibatis.logging.jdbc.BaseJdbcLogger:  ==> Parameters: 1(Integer)DEBUG  2017-06-17 10:30:42,137  org.apache.ibatis.logging.jdbc.BaseJdbcLogger:  <==      Total: 1the student is : FemaleStudent [studentHealthFemaleList=[StudentHealthFemale [id=1, studentId=1, checkDate=2017-0615, heart=好, liver=好, spleen=好, lung=好, kidney=好, uterus=好, note=1号女学生健康检查表]]]DEBUG  2017-06-17 10:30:42,137  org.apache.ibatis.transaction.jdbc.JdbcTransaction:  Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@79be0360]DEBUG  2017-06-17 10:30:42,138  org.apache.ibatis.transaction.jdbc.JdbcTransaction:  Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@79be0360]DEBUG  2017-06-17 10:30:42,138  org.apache.ibatis.datasource.pooled.PooledDataSource:  Returned connection 2042495840 to pool.

       注意看测试程序和日志,发现我只打印学生信息但是学生证,课程成绩,健康情况检查的sql都执行了,只缺一个课程没执行了。当我们加载学生信息时,它会根据鉴别器去找到健康的情况。

     这个时候aggressiveLazyLoading就可以用了,当它为true时,mybatis的内容按层级加载,否则就按我们调用的要求加载。所以这时候我们修改下mybatis的配置文件,代码如下:

   

<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE configuration  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"  "http://mybatis.org/dtd/mybatis-3-config.dtd"><configuration><properties resource="org/mybatis/config/database.properties" /><settings><setting name="logImpl" value="LOG4J" /><setting name="lazyLoadingEnabled" value="true"/><setting name="aggressiveLazyLoading" value="false"/></settings><!-- 定义类的别名 --><typeAliases><typeAlias type="org.mybatis.pojo.Role" alias="role" /></typeAliases><!-- 定义数据库信息,默认使用development数据库构建环境 --><environments default="development"><environment id="development"><!--采用jdbc事务管理 --><transactionManager type="JDBC"><property name="autoCommit" value="false" /></transactionManager><!-- 配置数据库连接信息 --><dataSource type="POOLED"><property name="driver" value="${driver}" /><property name="url" value="${url}" /><property name="username" value="${username}" /><property name="password" value="${password}" /></dataSource></environment></environments><mappers><mapper resource="org/mybatis/mapper/RoleMapper.xml" /><mapper resource="org/mybatis/mapper/userMapper.xml" /><mapper resource="org/mybatis/mapper/StudentMapper.xml" /><mapper resource="org/mybatis/mapper/StudentCardMapper.xml" /><mapper resource="org/mybatis/mapper/StudentLectureMapper.xml" /><mapper resource="org/mybatis/mapper/LectureMapper.xml" /><mapper resource="org/mybatis/mapper/MaleStudentMapper.xml" /><mapper resource="org/mybatis/mapper/FemaleStudentMapper.xml" /></mappers></configuration>

    把aggressiveLazyLoading设置为false,这时候就可以达到按需加载了。

   

    修改mybati的配置文件,设置的是全局的延迟加载,不能够指定哪些属性进行延迟加载。当然mybatis也提供了很人性化的属性,让我们可以指定属性进行延迟加载。我们可以在association和collection元素上加入属性值fetchType就可以了,它有两个取值,eager和lazy。一旦配置了它们,那么全局的变量就会被它们覆盖,这样我们就可以灵活的指定哪些东西可以立即加载,哪些可以延迟加载。



原创粉丝点击