1提前了解
传统JDBC编码格式
public class DataBaseUtil { public static final String URL = "jdbc:mysql://localhost:3306/mblog"; public static final String USER = "root"; public static final String PASSWORD = "123456"; public static void main(String[] args) throws Exception { Class.forName("com.mysql.jdbc.Driver"); //2. Connection conn = DriverManager.getConnection(URL, USER, PASSWORD); //3. Statement stmt = conn.createStatement(); //4. ResultSet rs = stmt.executeQuery("SELECT id, name, age FROM m_user where id =1"); //如果有数据,rs.next()返回true while(rs.next()){ System.out.println("name: "+rs.getString("name")+" 年龄:"+rs.getInt("age")); } } }
业务一多代码重复次数会非常之多
传统JDBC的问题
- 创建数据库的连接存在大量的硬编码,
- 执行statement时存在硬编码.
- 频繁的开启和关闭数据库连接,会严重影响数据库的性能,浪费数据库的资源.
- 存在大量的重复性编码
为了解决以上问题,就诞生了各种各样替换JDBC
的产品。即就是ORM框架。
什么是ORM?
全称为Object Relational Mapping。对象-映射-关系型数据库。对象关系映射(,简称ORM,或O/RM,或O/R mapping),用于实现面向对象编程语言里不同类型系统的数据之间的转换。简单的说,ORM是通过使用描述对象和数据库之间映射的元数据,将程序中的对象与关系数据库相互映射。
2Mybatis流程
3sqlsession详解,搞懂弄明白一半mybatis
mybatis每个sqlsession都有一个新的Executor,通过它来操控数据库,增删改是Executor的update方法执行,查询语句是用query方法。
SqlSession作为一个接口,其并没有线程安全性的问题,我们常说的线程安全问题是SqlSession的一个实现类DefaultSqlSession,mybatis的作者也对此类加以"Note that this class is not Thread-Safe"的注释。
此外SqlSession还有两个实现类SqlSessionManager和SqlSessionTemplate,这两个实现类是线程安全的。
线程不安全的DefaultSqlSession?
我们都知道DefaultSqlSession是线程不安全的,也会有很多博主讲解"SqlSessionTemplate是如何保证DefaultSqlSession线程安全的",但是DefaultSqlSession不安全的体现是什么?不安全产生的原因在哪?
首先看一下缓存流程和机制
什么是 一级缓存:
每当一个新 session 被创建,MyBatis 就会创建一个与之相关联的本地缓存。任何在 session 执行过的查询结果都会被保存在本地缓存中,所以,当再次执行参数相同的相同查询时,就不需要实际查询数据库了。本地缓存将会在做出修改、事务提交或回滚,以及关闭 session 时清空。(所以这里经常问缓存失效问题
- sqlsession变了 缓存失效
- sqlsession不变,查询条件不同,一级缓存失效
- sqlsession不变,中间发生了增删改操作,一级缓存失败(即执行了新一次事务并提交到数据库)
- sqlsession不变,手动清除缓存,一级缓存失败
)
所以问题就出现在这,即源码中的localCache, 查询时,由于第一次查询是不存在缓存的
Thread1进入query方法,用key取缓存localCache数据不存在,则进入了queryFromDatabase,并执行了"localCache.putObject(key, EXECUTION_PLACEHOLDER)",而此时Thread2进入了query方法,用key取缓存localCache数据,此时取出来的是Thread1刚缓存的EXECUTION_PLACEHOLDER,然后执行类型转换,由于EXECUTION_PLACEHOLDER不是list类型,所以转换抛出异常。
所以:
是BaseExecutor中缓存机制(mybatis的一级缓存)导致了并发问题。这种并发问题,产生原因:并发操作使用了同一个DefaultSqlSession的实例,而同一个DefaultSqlSession的实例使用的是同一个Executor对象,当缓存命中时就会出现异常或者数据不完整的情况。
那SqlSessionTemplate如何线程安全
SqlSessionTemplate是MyBatis专门为Spring提供的,支持Spring框架的一个SqlSession获取接口。主要是为了继承Spring,并同时将是否共用SqlSession的权限交给Spring去管理。查看getSqlSession()方法就知道每个线程对应的SqlSession都是私有的不会被共用,所以SqlSessionTemplate是线程安全的。
究其根本SqlSession真正的实现类只有DefaultSqlSession,SqlSessionManager和SqlSessionTemplate都是通过代理转发到DefaultSqlSession对应方法。
单例模式下的DefaultSqlSession不是线程安全的,SqlSessionManager和SqlSessionTemplate线程安全的根本就是每一个线程对应的SqlSession都是不同的。如果每一个操作都创建一个SqlSession对象,操作完又进行销毁导致性能极差。通过线程私有ThreadLocal存储SqlSession进行复用,从而提高性能。
结论:
每个线程都应该有它自己的SqlSession实例。SqlSession的实例不能共享使用,它也是线程不安全的。因此最佳的范围是请求或方法范围。绝对不能将SqlSession实例的引用放在一个类的静态字段或实例字段中。