Mybatis学习总结

Mybatis总结

1、Mybatis的初步使用

1.1引入依赖


<dependency>
    <groupId>org.mybatisgroupId>
    <artifactId>mybatisartifactId>
    <version>3.4.6version>
dependency>

1.2创建配置文件

在resource目录下创建mybatis-config.xml


DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
  
  <properties resource="jdbc.properties" />
  
	<typeAliases>
		<typeAlias type="com.tledu.zrz.model.User" alias="User"/>
		<typeAlias type="com.tledu.zrz.model.Address" alias="Address"/>
	typeAliases>
  <environments default="development">
    <environment id="development">
      <transactionManager type="JDBC"/>
      <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/example/BlogMapper.xml"/>
  mappers>
configuration>

创建jdbc.properties配置数据库

driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/erp16?useUnicode=true&characterEncoding=UTF-8
username=root
password=root

1.3创建xml文件并添加映射关系

<mappers>
	<mapper resource="mapper/User.xml"/>
mappers>

	

如果想把xml文件放到java目录中,则需要在maven的build中配置资源路径

<project>
  ...
  <build>
    ...
  	<resources>
            <resource>
                
                <directory>src/main/javadirectory>
                <includes>
                    
                    
                    <include>**/*.propertiesinclude>
                    <include>**/*.xmlinclude>
                includes>
                
                <filtering>falsefiltering>
            resource>
            <resource>
                <directory>src/main/resourcesdirectory>
            resource>
     resources>
    ...
  build>
  ...
project>

然后再编写一个servlet执行该操作

以上就是最简单的使用方法

1.4日志打印

由于mybatis对sql进行了封装,这个时候我们在项目运行过程中,如果出现问题了就需要进行日志的打印,在这里可以通过log4j这个工具打印对应sql日志,帮助我们进行错误的排查。

需要引入日志依赖


<dependency>
    <groupId>log4jgroupId>
    <artifactId>log4jartifactId>
    <version>1.2.17version>
dependency>

设置mybatis通过log4j打印日志

 <settings>
        <setting name="logImpl" value="LOG4J"/>
    settings>

在resource目录下添加log4j.properties配置文件

### Log4j配置 ###
#定义log4j的输出级别和输出目的地(目的地可以自定义名称,和后面的对应)
#[ level ] , appenderName1 , appenderName2
log4j.rootLogger=DEBUG,console,file
#-----------------------------------#
#1 定义日志输出目的地为控制台
log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.Target = System.out
log4j.appender.console.Threshold=DEBUG
####可以灵活地指定日志输出格式,下面一行是指定具体的格式 ###
#%c: 输出日志信息所属的类目,通常就是所在类的全名
#%m: 输出代码中指定的消息,产生的日志具体信息
#%n: 输出一个回车换行符,Windows平台为"/r/n",Unix平台为"/n"输出日志信息换行
log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=[%c]-%m%n
#-----------------------------------#
#2 文件大小到达指定尺寸的时候产生一个新的文件
log4j.appender.file = org.apache.log4j.RollingFileAppender
#日志文件输出目录
log4j.appender.file.File=log/info.log
#定义文件最大大小
log4j.appender.file.MaxFileSize=10mb
###输出日志信息###
#最低级别
log4j.appender.file.Threshold=ERROR
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=[%p][%d{yy-MM-dd}][%c]%m%n
#-----------------------------------#
#3 druid
log4j.logger.druid.sql=INFO
log4j.logger.druid.sql.DataSource=info
log4j.logger.druid.sql.Connection=info
log4j.logger.druid.sql.Statement=info
log4j.logger.druid.sql.ResultSet=info
#4 mybatis 显示SQL语句部分
log4j.logger.org.mybatis=DEBUG
#log4j.logger.cn.tibet.cas.dao=DEBUG
#log4j.logger.org.mybatis.common.jdbc.SimpleDataSource=DEBUG
#log4j.logger.org.mybatis.common.jdbc.ScriptRunner=DEBUG
#log4j.logger.org.mybatis.sqlmap.engine.impl.SqlMapClientDelegate=DEBUG
#log4j.logger.java.sql.Connection=DEBUG
log4j.logger.java.sql=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.ResultSet=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG

之后控制台就可以打印出sql语句了

1.5使用测试类

我们现在在测试mybatis的时候,每次都需要启动tomcat服务,每次还需要开发一个接口,就比较麻烦,这个时候我们可以基于JUnit进行单元测试,帮我们快速测试代码。

首先需要添加依赖


<dependency>
    <groupId>junitgroupId>
    <artifactId>junitartifactId>
    <version>4.12version>
    <scope>testscope>
dependency>

其次我们就可以创建测试类了。需要注意的是加标签,@Test @Before @After,还可以使用assertEquals进行断言操作

// 断定res = 1 如果res不等于1 则代表测试失败
assertEquals(res, 1);

2、Mapper的三种形式

常用的三种Mapper:

sqlSession执行对应语句(基本不会使用)

使用注解(简单的sql)

利用接口代理(绝大多数的选择)

2.1使用sqlSession执行

即上面演示的

2.2使用注解

  • 注解方式不需要User.xml

  • 需要再对应的接口上添加对应的注解语句

  • mybatis-config.xml中添加映射配置

在接口文件上直接添加注解

public interface IUserMapper {
    @Select("select * from t_user where id = #{id}")
    User getById(int id);
}

配置mybatis

<mappers>
    
 		<package name="com.tledu.erp.mapper"/>
 mappers>

运行测试样例

...  
// 调用xml中的sql
        User user = new User();
        user.setUsername("1234");
        user.setPassword("1234");
        user.setNickname("1234");
        User user1 = session.getMapper(IUserMapper.class).getById(1);
...

对于一些简单sql可以采用这种方式

2.3接口代理方式(常用)

  1. 需要xml

  2. 接口中不需要添加注解

  3. mybatis-config.xml中添加映射配置

创建User.xml


DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.tledu.erp.mapper.IUserMapper">
    <insert id="add" parameterType="User" >
        
        insert into t_user (username,password,nickname) values (#{username},#{password},#{nickname})
    insert>

    <select id="getList" resultType="User">
        select * from t_user
    select>

    <select id="getById" parameterType="int" resultType="User">
        select * from t_user where id = #{id}
    select>
mapper>

在mubatis-config.xml中配置

<mapper resource="com/tledu/erp/mapper/User.xml"/>

3、$ 和 # 的区别

3.1# { } 表示一个占位符号

  • 通过#{}可以实现 preparedStatement 向占位符中设置值,自动进行 java 类型和 jdbc 类型转换,

  • #{}可以有效防止 sql 注入。 #{}可以接收简单类型值或 pojo 属性值。

  • 可以自动对值添加 ’ ’ 单引号

3.2$ { } 表示拼接 sql 串

  • 通过${}可以将 parameterType 传入的内容拼接在 sql 中且不进行 jdbc 类型转换,

  • 可 以 接 收 简 单 类 型 值 或 p o j o 属 性 值 , 如 果 p a r a m e t e r T y p e 传 输 单 个 简 单 类 型 值 , {}可以接收简单类型值或 pojo 属性值,如果 parameterType 传输单个简单类型值, pojoparameterType{}括号中只能是 value。

  • 比如order by id 这种的,以id排序 那么这个id 是没有单引号的,就是简单的SQL拼接,所以我们应该使用${} 而不是#{}

总的来说一般都会使用#,如果需要进行字符串拼接则使用 , 并 且 可 以 调 用 c o n c a t ( ) 方 法 。 因 为 ,并且可以调用concat()方法。因为 concat有sql注入的风险

还有一点需要注意的是如果有多个参数或者引用类型则可以使用param()来指定参数的名称。

4、paramerterType 和 resultType

4.1paramerterType

表达传入参数的名称

4.2resultType

resultType 属性可以指定结果集的类型,它支持基本类型和实体类类型。

需要注意的是,它和 parameterType 一样,如果注册过类型别名的,可以直接使用别名。没有注册过的必须使用全限定类名。

同时,当是实体类名称是,还有一个要求,实体类中的属性名称必须和查询语句中的列名保持一致,否则无法实现封装。

4.3resultMap

我们在上一节,提到在声明返回值类型为实体类型之后,实体中的属性必须和查询语句中的属性对应上,但是我们在开发的过程中也难免会遇到无法对应的情况。比如说我们在进行数据库设计的时候,多个单词往往是用_连接,但是在实体类中的属性往往采用小驼峰的方式命名。这就导致字段名无法对应上,这个时候我们就需要配置resultMap来解决这个问题了。

通过resultMap,我们可以指定查询结果字段和实体属性字段的映射关系。

<resultMap id="userResult" type="User">

    <id column="id" property="id" />

    <result property="nickname" column="nickname" />

    <result property="schoolName" column="school_name" />

resultMap>

5、Mapper配置的三种方式

5.1Resource

使用相对于类路径的资源如:

5.2class

使用 mapper 接口类路径

如:

注意:此种方法要求 mapper 接口名称和 mapper 映射文件名称相同,且放在同一个目录中。

5.3package

注册指定包下的所有 mapper 接口

如:

注意:此种方法要求 mapper 接口名称和 mapper 映射文件名称相同,且放在同一个目录中,并且这里如果希望能够扫描到包下面的xml文件的话,需要在maven中进行配置。

6、动态Sql (重点)

6.1 if

<select id="list" parameterType="User" resultMap="userResult">
        select  * from t_user where 1=1
        <if test="username != null and username != ''">
            and username = #{username}
        if>
        <if test="nickname != null and nickname != ''">
            and nickname like concat('%',#{nickname},'%')
        if>
select>

6.2choose、when、otherwise

类似于Java中的switch case default

<select id="list" parameterType="User" resultMap="userResult">
        select * from t_user where 1=1
        <choose>
            <when test="id != null">
                and id = #{id}
            when>
            <when test="username != null and username != ''">
                and username = #{username}
            when>
            <otherwise>
                and nickname = #{nickname}
            otherwise>
        choose>
    select>

6.3 where、set

在我们where条件不确定的时候,我们每次都需要加上一个1=1才能保证后面的拼接不会出现错误,使用where标签后,我们就不需要考虑拼接的问题了,直接在里面添加条件即可。通过where标签也可以让代码更具有语义化,方便维护代码。set也一样

 <select id="list" parameterType="User" resultMap="userResult">
        select * from t_user where 
        <where>
            <if test="username != null and username != ''">
                and username = #{username}
            if>
            <if test="nickname != null and nickname != ''">
                and nickname like concat('%',#{nickname},'%')
            if>
        where>
    select>

6.4foreach

我们在开发项目中难免需要进行批量操作,这个时候就可以使用foreach进行循环添加。

    <insert id="batchInsert">
        insert into t_user (username, password, nickname) VALUES
        <foreach collection="list" index="idx" item="item" separator=",">
            (#{item.username},#{item.password},#{item.nickname})
        foreach>
    insert>

属性说明

  • collection 需要遍历的列表

  • item 每一项的形参名

  • index 每一项索引名

  • separtor 分隔符

  • open 开始符号

  • close 关闭符号

7、联查

7.1 1对1 联查

在实现1对1映射的时候,可以通过association属性进行设置。在这里有三种方式

在地址表中,每个地址对应有一个创建用户,每次查询地址的时候希望查询到创建用户的内容

7.1.1使用select
 <resultMap id="address" type="Address">
        <id column="id" property="id" />
        <association property="user" column="user_id" javaType="User" select="com.tledu.erp.dao.IUser2Dao.selectById"/>
resultMap>
  • property配置了实体类对应的属性

  • column配置了关联字段

  • select对应了IUser2Dao中的查询语句

在执行sql的过程中就会同时调用在IUserDao中定义的sql进行联查。

这种方式会执行两次sql语句,效率相对较低,同时还需要先在IUserDao中进行定义后才能使用,比较麻烦

7.1.2直接进行联查,在association中配置映射字段

这里可以直接写联查,需要转换的字段可以在association中进行配置。

    <resultMap id="address" type="Address" autoMapping="true">
        <id column="id" property="id" />
        <association property="user" column="user_id" javaType="User" >
          	<id column="user_id" property="id" />
            <result column="school_name" property="schoolName" />
        association>
    resultMap>

    <select id="selectOne" resultMap="address">
        select * from t_address left join t_user tu on tu.id = t_address.user_id where t_address.id = #{id}
    select>

autoType代表自动封装,如果不填写,则需要添加所有的对应关系。

这种方式的问题是,当association需要被多次引用的时候,就需要进行多次重复的配置,所以我们还有第三种方式,引用resultMap。

7.1.3嵌套的resultType
<resultMap id="addressMap" type="Address" autoMapping="true">
        <id column="id" property="id"/>
        <association property="user" column="user_id" resultMap="userMap">
        association>
    resultMap>

    <resultMap id="userMap" type="User" autoMapping="true">
        <id column="user_id" property="id" />
        <result column="school_name" property="schoolName"/>
    resultMap>

    <select id="selectOne" resultMap="addressMap">
        select t_address.id,
               addr,
               phone,
               postcode,
               user_id,
               username,
               password,
               nickname,
               age,
               sex,
               school_name
        from t_address
                 left join t_user tu on tu.id = t_address.user_id
        where t_address.id = #{id}
    select>

这种写法就是第二种的拆分版本,通过这种方式,userMap就可以被多次引用了。

7.2 一对多联查

对于address来说,一个地址对应一个创建用户,但是对于User来说,一个用户可能对应创建了多条地址信息,这种情况,在mybatis中就可以通过collections解决。

同样也是有三种方法

7.2.1使用select

这里的用法和1对1的时候一致

在AddressDao中添加

    <select id="selectByUserId" resultType="Address">
      select * from t_address where user_id = #{userId}
    select>

在UserDao中添加

    <resultMap id="userResult" type="User" autoMapping="true">
        <id column="id" property="id"/>
        <result property="nickname" column="nickname"/>
        <result property="schoolName" column="school_name"/>
        <collection property="addressList" column="id" autoMapping="true" select="com.tledu.erp.dao.IAddressDao.selectByUserId" >
        collection>
    resultMap>


    <select id="selectById" parameterType="int" resultMap="userResult">
        select *
        from t_user
        where id = #{id}
    select>
7.2.2直接进行联查,在collection中配置映射字段
 <resultMap id="userResult" type="User" autoMapping="true">
        <id column="id" property="id"/>
        <result property="nickname" column="nickname"/>
        <result property="schoolName" column="school_name"/>
        <collection property="addressList" column="phone" ofType="Address" autoMapping="true">
            <id column="address_id" property="id" />
        collection>
    resultMap>


    <select id="selectById" parameterType="int" resultMap="userResult">
        select tu.id,
               username,
               password,
               nickname,
               age,
               sex,
               school_name,
               ta.id as address_id,
               addr,
               phone,
               postcode,
               user_id
        from t_user tu
                 left join t_address ta on tu.id = ta.user_id
        where tu.id = #{id}
    select>

autoMapping代表自动封装,如果不填写,则需要添加所有的对应关系。

这里需要配置ofType来指定返回值类型

这种方式的问题是,当collection需要被多次引用的时候,就需要进行多次重复的配置,所以我们还有第三种方式,引用resultMap。

7.2.3嵌套的resultType
 <resultMap id="userResult" type="User" autoMapping="true">
        
        <result property="nickname" column="nickname"/>
        <result property="schoolName" column="school_name"/>
        <collection property="addressList" column="phone" ofType="Address" resultMap="addressResultMap" autoMapping="true">
        collection>
    resultMap>

    <resultMap id="addressResultMap" type="Address" autoMapping="true">
        <id column="address_id" property="id" />
    resultMap>


    <select id="selectById" parameterType="int" resultMap="userResult">
        select tu.id,
               username,
               password,
               nickname,
               age,
               sex,
               school_name,
               ta.id as address_id,
               addr,
               phone,
               postcode,
               user_id
        from t_user tu
                 left join t_address ta on tu.id = ta.user_id
        where tu.id = #{id}
    select>

通过这种方式,userMap就可以被多次引用了。

8、Mybatis中的连接池

1.1 什么是连接池

数据库连接是一项有限的昂贵资源,一个数据库连接对象均对应一个物理数据库连接,每次操作都打开一个物理连接,使用完都关闭连接,这样造成系统的性能低下。

数据库连接池的解决方案是在应用程序启动时建立足够的数据库连接,并将这些连接组成一个连接池,由应用程序动态地对池中的连接进行申请、使用和释放。

对于多于连接池中连接数的并发请求,应该在请求队列中排队等待。并且应用程序可以根据池中连接的使用率,动态增加或减少池中的连接数。

总结:

  1. 连接池是面向数据库连接的
  2. 连接池是为了优化数据库连接资源

1.2 Mybatis中的连接池

在 Mybatis 中也有连接池技术,但是它采用的是自己的连接池技术。在 Mybatis 的配置文件中,通过Mybatis学习总结_第1张图片

在mybatis中数据库连接池可以分为以下三类

  • UNPOOLED 不使用连接池的数据源

  • POOLED 使用连接池的数据源

  • JNDI 使用JNDI实现的数据库连接池

1.3 UNPOOLED连接过程分析

UNPOOLED 不使用连接池的数据源,当 dateSource 的type属性被配置成了UNPOOLED,MyBatis 首先会实例化一个UnpooledDataSourceFactory工厂实例,然后通过.getDataSource() 方法返回一个UnpooledDataSource 实例对象引用,我们假定为dataSource。

9、mybatis中的事务和隔离级别

9.1 jdbc中的事务

在 JDBC 中我们可以通过手动方式将事务的提交改为手动方式,通过 setAutoCommit()方法就可以调整。 通过 JDK 文档,我们找到该方法如下:

Mybatis学习总结_第2张图片

那么我们的 Mybatis 框架因为是对 JDBC 的封装,所以 Mybatis 框架的事务控制方式,本身也是用 JDBC的 setAutoCommit()方法来设置事务提交方式的。

9.2mybatis中的事务提交方式

Mybatis 中事务的提交方式,本质上就是调用 JDBC 的 setAutoCommit()来实现事务控制。 我们运行之前所写的代码:

   //用于在测试方法执行之前执行
    @BeforeEach
    public void init()throws Exception{
        // 设置为自动提交
        sqlSession = MybatisUtils.openSession(true);
        //获取dao的代理对象
        userMapper = sqlSession.getMapper(IUserMapper.class);
    }

    // 在测试结束之后执行
    @AfterEach
    public void destroy()throws Exception{
        //提交事务
		//sqlSession.commit();
        //释放资源
        sqlSession.close();
    }


    @Test
    public void testCommit(){
        User condition = new User();
        condition.setId(1);
        condition.setNickname("尚云科技1112");
        int i = userMapper.update(condition);
        assertEquals(i,1);
    }

@BeforeEach: 在每个方法之前加一下些操作

@AfterEach: 在每个方法之后加一些操作

这里我们设置了自动提交事务之后,就不需要在进行commit操作了

9.3事务的回滚

对于我们开发过程,也会遇到报错的情况,这个时候为了保证数据的一致性我们就需要进行事务的回滚,比如我们有一个操作需要同时更新用户表和地址表,这个时候更新用户表的时候成功了,但是更新地址表的时候出现了一个特别长的字段,导致更新失败,这个时候,我们需要将数据进行回滚。

 //用于在测试方法执行之前执行
    @BeforeEach
    public void init()throws Exception{
        // 设置为自动提交
        sqlSession = MybatisUtils.openSession();
        //获取dao的代理对象
        userMapper = sqlSession.getMapper(IUserMapper.class);
    }

    // 在测试结束之后执行
    @AfterEach
    public void destroy()throws Exception{
        //提交事务
        sqlSession.commit();
        //释放资源
        sqlSession.close();
    }

    @Test
    public void testRollback(){
        try{
            User condition = new User();
            condition.setId(1);
            condition.setNickname("尚云科技111299");
            int i = userMapper.update(condition);
            assertEquals(i,1);
            throw new Exception();
        }catch (Exception e){
            e.printStackTrace();
            // 进行数据回滚操作
            sqlSession.rollback();
        }
    }

9.4事务的隔离级别

在我们学习数据库的时候,涉及到事务这一模块的时候经常会涉及到的三个名词就是

  • 脏读

  • 不可重复读

  • 幻读

脏读 :

脏读就是指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问 这个数据,然后使用了这个数据。

不可重复读

是指在一个事务内,多次读同一数据。在这个事务还没有结束时,另外一个事务也访问该同一数据。那么,在第一个事务中的两 次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的的数据可能是不一样的。这样就发生了在一个事务内两次读到的数据是不一样的,因此称为是不 可重复读。例如,一个编辑人员两次读取同一文档,但在两次读取之间,作者重写了该文档。当编辑人员第二次读取文档时,文档已更改。原始读取不可重复。如果 只有在作者全部完成编写后编辑人员才可以读取文档,则可以避免该问题。

幻读

是指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。 同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好象 发生了幻觉一样。例如,一个编辑人员更改作者提交的文档,但当生产部门将其更改内容合并到该文档的主复本时,发现作者已将未编辑的新材料添加到该文档中。 如果在编辑人员和生产部门完成对原始文档的处理之前,任何人都不能将新材料添加到文档中,则可以避免该问题。

事务隔离

这个时候我们就需要设置事务的隔离级别来解决这个问题

√: 可能出现 ×: 不会出现

脏读 不可重复读 幻读 说明
Read uncommitted 直译就是"读未提交",意思就是即使一个更新语句没有提交,但是别 的事务可以读到这个改变.这是很不安全的。允许任务读取数据库中未提交的数据更改,也称为脏读。
Read committed × 直译就是"读提交",可防止脏读,意思就是语句提交以后即执行了COMMIT以后,别的事务才能读到这个改变. 只能读取到已经提交的数据。Oracle等多数数据库默认都是该级别
Repeatable read × × 直译就是"可以重复读",这是说在同一个事务里面先后执行同一个查询语句的时候,得到的结果是一样的.在同一个事务内的查询都是事务开始时刻一致的,InnoDB默认级别。在SQL标准中,该隔离级别消除了不可重复读,但是还存在幻象读。
Serializable × × × 直译就是"序列化",意思是说这个事务执行的时候不允许别的事务并发执行. 完全串行化的读,每次读都需要获得表级共享锁,读写相互都会阻塞

mybatis中也可以设置隔离级别,只不过增加了一个没有事务的属性

package org.apache.ibatis.session;

public enum TransactionIsolationLevel {
    NONE(0),
    READ_COMMITTED(2),
    READ_UNCOMMITTED(1),
    REPEATABLE_READ(4),
    SERIALIZABLE(8);

    private final int level;

    private TransactionIsolationLevel(int level) {
        this.level = level;
    }

    public int getLevel() {
        return this.level;
    }
}

9.5延迟加载策略

延迟加载:

就是在需要用到数据时才进行加载,不需要用到数据时就不加载数据。延迟加载也称懒加载.

好处:

先从单表查询,需要时再从关联表去关联查询,大大提高数据库性能,因为查询单表要比关联查询多张表速度要快。

坏处:

因为只有当需要用到数据时,才会进行数据库查询,这样在大批量数据查询时,因为查询工作也要消耗时间,所以可能造成用户等待时间变长,造成用户体验下降。

需求:

查询地址(Address)信息并且关联查询用户(User)信息。

如果先查询地址(Address)信息即可满足要求,当我们需要查询用户(User)信息时再查询用户(User)信息。把对用户(User)信息的按需去查询就是延迟加载。

实现多表操作时,我们使用了resultMap来实现一对一,一对多关系的操作。主要是通过 association、collection 实现一对一及一对多映射。

association、collection 具备延迟加载功能。

9.5.1association实现延迟加载

通过lazyLoadingEnabled、aggressiveLazyLoading可以对延迟加载进行配置

  <settings>
        <setting name="logImpl" value="LOG4J"/>
        <setting name="lazyLoadingEnabled" value="true"/>
        <setting name="aggressiveLazyLoading" value="false"/>
    settings>

添加了懒加载配置之后,我们在进行列表查询的过程中就不会再做大量的关联查询了,可以提升列表查询的效率,在我们用到具体字段之后才会进行关联查询。

10、使用注解进行开发

10.1mybatis常用注解

@Insert:实现新增

@Update:实现更新

@Delete:实现删除

@Select:实现查询

@Result:实现结果集封装

@Results:可以与@Result 一起使用,封装多个结果集

@ResultMap:实现引用@Results 定义的封装

@One:实现一对一结果集封装

@Many:实现一对多结果集封装

10.2实现基本的增删改查

public interface IAddressDao {
    @Insert("insert into t_address (addr, phone, postcode, user_id) VALUES (#{addr},#{phone},#{postcode},#{userId})")
    int insert(Address address);

    @Delete("delete from t_address where id = #{id}")
    int delete(int id);

    @Update("update t_address set addr = #{addr} where id = #{id}")
    int update(Address address);

    @Select("select * from t_address where id = #{id}")
    Address selectById(int id);
}

10.3使用Result进行映射

    @Select("select * from t_address where id = #{id}")
    @Results(id = "addressRes", value = {
            //id = true 标志这个字段是主键
            @Result(id = true, column = "id", property = "id"),
            @Result(column = "addr", property = "addr"),
            @Result(column = "phone", property = "mobile"),
    })
    Address selectById(int id);

11、缓存

像大多数的持久化框架一样,Mybatis 也提供了缓存策略,通过缓存策略来减少数据库的查询次数,从而提高性能。

Mybatis 中缓存分为一级缓存,二级缓存

11.1一级缓存

一级缓存是 SqlSession 级别的缓存,只要 SqlSession 没有 flush 或 close,它就存在。

一级缓存是 SqlSession 范围的缓存,当调用 SqlSession 的修改,添加,删除,commit(),close()等方法时,就会清空一级缓存。

11.2二级缓存

二级缓存是 mapper 映射级别的缓存,多个 SqlSession 去操作同一个 Mapper 映射的 sql 语句,多个SqlSession 可以共用二级缓存,二级缓存是跨 SqlSession 的。

首先开启 mybatis 的二级缓存。

sqlSession1 去查询用户信息,查询到用户信息会将查询数据存储到二级缓存中

如果 SqlSession3 去执行相同 mapper 映射下 sql,执行 增删改的commit 提交,将会清空该 mapper 映射下的二级缓存区域的数据。

当然clearCache() 不会清空二级缓存的数据,如果希望清空二级缓存

sqlSession2 去查询与 sqlSession1 相同的用户信息,首先会去缓存中找是否存在数据,如果存在直接从缓存中取出数据

开启二级缓存

<setting name="cacheEnabled" value="true"/>

在映射文件中开启缓存支持

<mapper namespace="com.tledu.erp.mapper.IAddressMapper">
  
    <cache />
mapper>

查询语句中需要指定useCache=“true”

<select id="selectOne" resultMap="addressResultMap" useCache="true">
        select *
        from t_address
        where id = #{id}
select>

当我们在使用二级缓存时,所缓存的类一定要实现 java.io.Serializable 接口,这种就可以使用序列化方式来保存对象。

总结:

  • 在使用二级缓存的时候,需要注意配置mybatis-config.xml中 开启二级缓存

  • 然后再mapper映射文件中使用catch标签标注开启,并对需要换成的语句添加useCache=”true”

  • 在mapper的映射文件中使用,代表当前mapper是开启二级缓存的

  • 在需要二级缓存的查询上增加useCache = true,代表当前查询是需要缓存的

  • 并且对应封装数据的实体类需要实现Serializable 接口

  • 对待缓存的数据,实现Serialization接口,代表这个数据是可序列化

  • 只有当sqlSession close之后,二级缓存才能生效

  • 当执行增删改操作的时候,必须执行commit()才能持久化到数据库中,同时二级缓存清空

  • session.clearCache()无法清除二级缓存,如果需要清除二级缓存,可以通过sqlSessionFactory.getConfiguration().getCache(“缓存id”).clear();

  • 但是当我们查询语句中,执行commit() 或者是close()关闭session,都不会清空二级缓存

12、分页

数据特别多的时候,单次请求返回大量的数据接口会非常慢。

对于数据量特别大的查询,我们都会采用分页查询

12.1分页的设计

  • 每页有多少个

  • 当前是在第几页

  • 数据的总数

  • 数据列表

基于这些属性设计分页的实体类

@Data
public class PageInfo<T> {
    /**
     * 每页有多少个
     */
    private int pageSize;
    /**
     * 当前是在第几页
     */
    private int currentPage;
    /**
     * 数据的总数
     */
    private int total;
    /**
     * 数据列表
     */
    private List<T> list;
    
    // 获取偏移量
    public int getOffset() {
        return (this.currentPage - 1) * this.pageSize;
    }
}

使用插件实现

1 引入依赖

 <dependency>
            <groupId>com.github.pagehelpergroupId>
            <artifactId>pagehelperartifactId>
            <version>5.2.0version>
        dependency>

2 配置拦截器

在mybatis的配置文件中增加插件


<plugins>
    
    <plugin interceptor="com.github.pagehelper.PageInterceptor">
        
        <property name="param1" value="value1"/>
	plugin>
plugins>

插件的使用

    @Test
    public void testList() throws IOException {
        SqlSession session = MybatisUtils.openSession();
        User condition = new User();
        // 插件里提供的分页工具,在要查询之前,执行一下PageHelper.startPage(当前页数,每页的容量), 当使用工具时候,会导致懒加载失败
        // 加了这个操作,插件就会在sql语句中拼接limit限制,并且还会统计总个数
        PageHelper.startPage(1,5);
        List<User> users = session.getMapper(IUserMapper.class).list(condition);
        // 拿到结果之后通过PageInfo.of() 的方法,获得pageInfo
        com.github.pagehelper.PageInfo<User> list = com.github.pagehelper.PageInfo.of(users);
        System.out.println(users);
    }

你可能感兴趣的:(Mybatis,数据库架构,数据仓库,mybatis)