mybatis

MyBatis的事物管理和缓存

2019-10-28  本文已影响0人  Dcl_Snow

MyBatis的事物

事物的概念

在Java语言数据库框架中,数据库的事务管理都是非常重要的。
每个业务逻辑都是由一系列数据库访问完成的,这些访问可能修改多条数据记录,这一系列修改应该是一个整体,绝对不能只修改其中的某几条数据记录。
多个数据库原子访问应该被绑定成一个整体,这就是事物。事务是一步或几步操作组成的逻辑执行单元,这些基本操作作为一个整体执行单元,它们要么全部执行,要么全部取消执行,绝对不能仅仅执行一部分。
一个用户请求对应一个业务逻辑方法,一个逻辑方法往往具有逻辑上的原子性,此时应使用事物。
例如:一个转账操作,对应修改两个账户余额,这两个账户的修改要么同时生效,要么同时取消,同时生效是转账成功,同时取消是转账失败;但不可只修改其中一个账户,那将破坏数据库的完整性。

事物的四个特性

Transaction接口

对数据库事物而言,应具有:创建、提交、回滚、关闭几个动作,MyBatis的事物设计重点是org.apache.ibatis.transaction.Transaction接口,该接口源码如下:

public interface Transaction {
    Connection getConnection() throws SQLException;

    void commit() throws SQLException;

    void rollback() throws SQLException;

    void close() throws SQLException;

    Integer getTimeout() throws SQLException;
}

Transaction接口有两个实现类:org.apache.ibatis.transaction.jdbc.JdbcTransaction和org.apache.ibatis.transaction.managed.ManagedTransaction。
所以MyBatis的事务管理有两种形式:

事物的创建和使用

在使用MyBatis的时候,会在MyBatis的配置文件mybatis-config.xml中定义,此处使用前文(https://www.jianshu.com/p/063a5ca8874c)配置信息:

<environment id="mysql">
            <!--指定事务管理的类型,这里简单使用Java的JDBC的提交和回滚设置-->
            <transactionManager type="JDBC"></transactionManager>
            <!--dataSource 指连接源配置,POOLED是JDBC连接对象的数据源连接池的实现-->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"></property>
                <property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis?useUnicode=true&amp;characterEncoding=UTF-8"></property>
                <property name="username" value="root"></property>
                <property name="password" value="****"></property>
            </dataSource>
        </environment>

<environment>元素定义了连接数据库的信息,<transactionManager>子元素的type决定了使用什么类型的事物管理机制。

MyBatis的缓存

缓存的概述

在实际项目开发中,通常对数据库查询的性能要求很高,MyBatis提供了查询缓存来进行数据的缓存,以达到提高查询性能的要求。
MyBatis的查询缓存分为一级缓存和二级缓存:

MyBatis通过缓存机制减轻数据压力,提高数据库性能。

一级缓存

在操作数据库时需要构造SqlSession对象,在对象中有一个HashMap用户缓存数据。不同的SqlSession之间的缓存数据区域(HashMap)是互相不影响的。
一级缓存的作用是SqlSession范围的,当同一个SqlSession中执行两次相同的sql语句时,第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次查询时会从缓存中获取数据,不再去底层数据库查询,提高查询效率。
\color{red}{注意:}
若SqlSession执行了DML操作(insert、update和delete),并提交到数据库,MyBatis则会清空SqlSession中的一级缓存,目的是为了保证缓存中存储的是最新的数据,避免脏读现象。
当一个SqlSession结束后,该SqlSession中的一级缓存也就不存在了。
\color{red}{MyBatis默认开启一级缓存,不需要进行任何配置。}

一级缓存测试

项目代码使用前文项目(https://www.jianshu.com/p/063a5ca8874c
现在数据库的tb_user表中插入几条数据:

MyBatis01.png
然后在项目的UserMapper.xml文件中添加查询和删除程序,完整程序如下:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.snow.dcl.mapper.UserMapper">
    <!--插入用户数据-->
    <insert id="saveUser" parameterType="com.snow.dcl.domain.User" useGeneratedKeys="true">
        insert into tb_user(name,sex,age) values (#{name},#{sex},#{age});
    </insert>
    <!--根据id查询用户-->
    <select id="selectUserById" parameterType="int" resultType="com.snow.dcl.domain.User">
        select * from tb_user where id=#{id};
    </select>
    <!--查询所有用户-->
    <select id="selectAllUser" resultType="com.snow.dcl.domain.User">
        select * from tb_user;
    </select>
    <!--根据id删除用户-->
    <delete id="deleteUserById" parameterType="int">
        delete from tb_user where id=#{id};
    </delete>
</mapper>

在项目的java目录右键,创建com.snow.dcl.mapper包,在该包中创建UserMapper.java接口类:


MyBatis02.png

编写如下程序:

public interface UserMapper {
    //根据id查询用户
    User selectUserById(Integer id);
    //查询所有用户
    List<User> selectAllUser();
    //根据id删除用户
    void deleteUserById(Integer id);
}

获取mybatis-config.xml配置文件,根据配置文件创建SqlSessionFactory,获取SqlSession对象这一系列操作,每次都要使用,所以将其封装在一个类文件中,在项目java目录右键,创建com.snow.dcl.utils包,在该包下创建FactoryUtil.java类文件:


MyBatis03.png

添加如下程序:

public class FactoryUtil {
    private static SqlSessionFactory sqlSessionFactory = null;

    static {
        try {
            InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static SqlSession getSqlSession(){
        return sqlSessionFactory.openSession();
    }
}

在项目的test目录下的java目录下创建OneLevelCacheTest.java测试类文件:


MyBatis04.png

编写如下程序:

public class OneLevelCacheTest {
    public static void main(String[] args) {
        OneLevelCacheTest oneLevelCacheTest = new OneLevelCacheTest();
        oneLevelCacheTest.cacheOneTest();
    }

    public void cacheOneTest(){
        SqlSession sqlSession = FactoryUtil.getSqlSession();
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        User user = userMapper.selectUserById(1);
        System.out.println(user);
        User anotherUser = userMapper.selectUserById(1);
        System.out.println(anotherUser);
        sqlSession.close();
    }
}

执行测试程序OneLevelCacheTest1.java,可以在控制台看到打印结果:


MyBatis05.png

可以看到在第一次执行查询id为1的User对象时,执行了一条select语句,第二次执行查询id为1的User对象时,没有执行select语句,因为此时一级缓存中已经缓存了id为1的User对象,MyBatis直接从缓存中将User对象取出来,并没有再次去数据库中查询。

DML操作清空缓存

在项目的test目录下的java目录创建OneLevelCacheTest2测试类文件,编写如下代码:

public class OneLevelCacheTest2 {
    public static void main(String[] args) {
        OneLevelCacheTest2 oneLevelCacheTest = new OneLevelCacheTest2();
        oneLevelCacheTest.cacheOneTest();
    }

    public void cacheOneTest(){
        SqlSession sqlSession = FactoryUtil.getSqlSession();
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        User user = userMapper.selectUserById(1);
        System.out.println(user);
        userMapper.deleteUserById(7);
        sqlSession.commit();
        User anotherUser = userMapper.selectUserById(1);
        System.out.println(anotherUser);
        sqlSession.close();
    }
}

执行测试程序OneLevelCacheTest2.java,可以在控制台看到打印结果:


MyBatis06.png

可以看到在第一次执行查询id为1的User对象时,执行了一条select语句,接下来执行了一个delete操作,MyBatis为了保证缓存中存储的是最新的数据,清空了一级缓存,所以第二次执行查询id为1的User对象时,又执行了select语句。

不同Session对象对一级缓存的影响

在项目的test目录下的java目录创建OneLevelCacheTest3测试类文件,编写如下代码:

public class OneLevelCacheTest3 {
    public static void main(String[] args) {
        OneLevelCacheTest3 oneLevelCacheTest = new OneLevelCacheTest3();
        oneLevelCacheTest.cacheOneTest();
    }

    public void cacheOneTest(){
        SqlSession sqlSession = FactoryUtil.getSqlSession();
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        User user = userMapper.selectUserById(1);
        System.out.println(user);
        sqlSession.close();
        sqlSession = FactoryUtil.getSqlSession();
        userMapper = sqlSession.getMapper(UserMapper.class);
        User anotherUser = userMapper.selectUserById(1);
        System.out.println(anotherUser);
        sqlSession.close();
    }
}

执行测试程序OneLevelCacheTest2.java,可以在控制台看到打印结果:


MyBatis07.png

可以看到在第一次执行查询id为1的User对象时,执行了一条select语句,接下来调用了sqlSession.close()关闭了一级缓存,第二次执行查询id为1的User对象时,一级缓存是一个新的对象,缓存中没有缓存任何数据,所以再次执行了select语句。

二级缓存

使用二级缓存时,多个SqlSession使用同一个Mapper的sql语句去操作数据库,得到的数据会存在二级缓存区域,它同样是使用HashMap进行数据存储。相比一级缓存SqlSession,二级缓存的范围更大,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。
二级缓存是多个SqlSession共享的,其作用域是mapper的同一个namespace。不同的SqlSession两次执行相同namespace下的sql语句,且向sql中传递的参数也相同,即最终执行相同的sql语句,则第一次执行完毕会将数据库中查询的数据写入缓存,第二次查询时会从缓存中获取数据,不再去底层数据库查询,提高效率。
\color{red}{MyBatis默认没有开启二级缓存,需要在setting全局参数中进行配置,开启二级缓存。}

二级缓存测试

在mubatis-config.xml配置文件中开启二级缓存,完整配置文件如下:

<configuration>
    <!-- 指定Mybatis所用日志的具体实现 -->
    <settings>
        <!--开启二级缓存-->
        <setting name="cacheEnabled" value="true"/>
        <!--开启日志-->
        <setting name="logImpl" value="Log4J"/>
    </settings>
    <!--环境配置,连接的数据库-->
    <environments default="mysql">
        <environment id="mysql">
            <!--指定事务管理的类型,这里简单使用Java的JDBC的提交和回滚设置-->
            <transactionManager type="JDBC"></transactionManager>
            <!--dataSource 指连接源配置,POOLED是JDBC连接对象的数据源连接池的实现-->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"></property>
                <property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis?useUnicode=true&amp;characterEncoding=UTF-8"></property>
                <property name="username" value="root"></property>
                <property name="password" value="Password@123"></property>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <!--告诉Mybatis持久化类的映射文件路径-->
        <mapper resource="mapping/UserMapper.xml"></mapper>
    </mappers>
</configuration>

cacheEnabled默认为false,设置为true表示开启二级缓存。
在UserMapper.sml文件配置缓存相关参数,完整配置文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.snow.dcl.mapper.UserMapper">
    <!--开启当前mapper的namespace下的二级缓存-->
    <cache eviction="LRU" flushInterval="20000" size="512" readOnly="true"/>
    <!--插入用户数据-->
    <insert id="saveUser" parameterType="com.snow.dcl.domain.User" useGeneratedKeys="true">
        insert into tb_user(name,sex,age) values (#{name},#{sex},#{age});
    </insert>
    <!--根据id查询用户-->
    <select id="selectUserById" parameterType="int" resultType="com.snow.dcl.domain.User">
        select * from tb_user where id=#{id};
    </select>
    <!--查询所有用户-->
    <select id="selectAllUser" resultType="com.snow.dcl.domain.User">
        select * from tb_user;
    </select>
    <!--根据id删除用户-->
    <delete id="deleteUserById" parameterType="int">
        delete from tb_user where id=#{id};
    </delete>
</mapper>

参数解释:

public class TwoLevelCacheTest1 {
    public static void main(String[] args) {
        TwoLevelCacheTest1 twoLevelCacheTest = new TwoLevelCacheTest1();
        twoLevelCacheTest.cacheTwoTest();
    }

    public void cacheTwoTest(){
        SqlSession sqlSession = FactoryUtil.getSqlSession();
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        User user = userMapper.selectUserById(1);
        System.out.println(user);
        sqlSession.close();
        sqlSession = FactoryUtil.getSqlSession();
        userMapper = sqlSession.getMapper(UserMapper.class);
        User anotherUser = userMapper.selectUserById(1);
        System.out.println(anotherUser);
        sqlSession.close();
    }
}

执行测试程序TwoLevelCacheTest.java,可以在控制台看到打印结果:

MyBatis08.png
可以看到在第一次执行查询id为1的User对象时,执行了一条select语句,接下来调用了sqlSession.close()关闭了一级缓存,第二次执行查询id为1的User对象时,一级缓存没有任何对象,但因为启用了二级缓存,第一次查询的数据会缓存在二级缓存中,所以显示命中二级缓存数据,不需要在执行select语句。

在UserMapper.xml文件的select语句中设置useCache='false',可以禁用当前select语句的二级缓存,即每次都会查询数据库,默认为true,配置内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.snow.dcl.mapper.UserMapper">
    <!--开启当前mapper的namespace下的二级缓存-->
    <cache eviction="LRU" flushInterval="20000" size="512" readOnly="true"/>
    <!--插入用户数据-->
    <insert id="saveUser" parameterType="com.snow.dcl.domain.User" useGeneratedKeys="true">
        insert into tb_user(name,sex,age) values (#{name},#{sex},#{age});
    </insert>
    <!--根据id查询用户-->
    <select id="selectUserById" parameterType="int" resultType="com.snow.dcl.domain.User" useCache="false">
        select * from tb_user where id=#{id};
    </select>
    <!--查询所有用户-->
    <select id="selectAllUser" resultType="com.snow.dcl.domain.User">
        select * from tb_user;
    </select>
    <!--根据id删除用户-->
    <delete id="deleteUserById" parameterType="int">
        delete from tb_user where id=#{id};
    </delete>
</mapper>
上一篇 下一篇

猜你喜欢

热点阅读