文章目录
- 1. 前言
- 2. 先说结论
- 3. 例子
- 1. 准备数据
- 2. 思考过程
- 3. 结论
1. 前言
-
当前在使用springboot+mybatis的时候,通常会先在配置文件中配置好数据源,并在Mapper.xml文件编写好相关SQL,使用mybatis进行对数据库进行所谓的crud操作。
-
有时候会出现一个项目需要跟多个数据源进行相关操作,通常是使用JdbcTemplate对另一个数据源进行crud相关操作:
-
虽也可以实现功能,但将SQL写在类里面,与本数据源将SQL写到了XML文件中,总是感觉格格不入,为了统一项目SQL的存放位置,进而研究了SqlSessionFactory.openSession(Connection connection)方法。
2. 先说结论
-
在mybatis中SqlSessionFactory中有个方法是 SqlSessionFactory.openSession(Connection connection)方法,可以直接给个数据库连接创建SqlSession。
-
当拥有了SqlSession,可以使用SqlSession.getMapper(Class clazz) 来获取所谓的Mapper对应的dao层。
-
但遗憾的是,无法使用springboot帮我们创建的bean对象SqlSessionFactory,因为其默认是使用SpringManagedTransaction事务,在SqlSessionFactory.openSession(Connection connection)会报错:New spring transactions require a DataSource.
-
所以只能自己创建如下:
@org.junit.Test public void selectAll() throws Exception{ // 其他数据源 DriverManagerDataSource dataSource = new DriverManagerDataSource(); dataSource.setUrl("jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC"); dataSource.setUsername("root"); dataSource.setPassword("HelloWorld"); // 生成SqlSessionFactoryBean对象,为了创建SqlSessionFactory SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); // 数据源 sqlSessionFactoryBean.setDataSource(dataSource); // 接口对应mapper的路径 sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mappers/*.xml")); // 设置事务工厂类,不使用SpringManagedTransaction,原因思考过程已说 sqlSessionFactoryBean.setTransactionFactory(new JdbcTransactionFactory()); // 获取SqlSessionFactory对象,为了创建SqlSession对象 final SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBean.getObject(); // 创建SqlSession final SqlSession sqlSession = sqlSessionFactory.openSession(dataSource.getConnection()); // 获取mapper对应的dao层接口 final UserDao userDao = sqlSession.getMapper(UserDao.class); final List<User> users = userDao.selectAll(); System.out.println(users); }
-
封装为工具类:
private <T> T getMapper(DataSource dataSource, Class<T> clazz) throws Exception { SqlSession sqlSession = createSqlSession(dataSource); return sqlSession.getMapper(clazz); } private SqlSession createSqlSession(DataSource dataSource) throws Exception { // 生成SqlSessionFactoryBean对象,为了创建SqlSessionFactory SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); // 数据源 sqlSessionFactoryBean.setDataSource(dataSource); // 接口对应mapper的路径 sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mappers/*.xml")); // 设置事务工厂类,不使用SpringManagedTransaction,原因思考过程已说 sqlSessionFactoryBean.setTransactionFactory(new JdbcTransactionFactory()); // 获取SqlSessionFactory对象,为了创建SqlSession对象 final SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBean.getObject(); // 创建SqlSession return sqlSessionFactory.openSession(dataSource.getConnection()); }
3. 例子
1. 准备数据
-
准备一张表如下:
-
准备好项目并写好对应的Mapper,如下:
// dao层 @Mapper public interface UserDao { List<User> selectAll(); }
<!--UserDao.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.example.csdn2.mybatis_sqlSession_test.dao.UserDao"> <select id="selectAll" resultType="com.example.csdn2.mybatis_sqlSession_test.User"> select * from user2 </select> </mapper>
2. 思考过程
-
一般我们使用mybatis会如下:
先将yml配置文件配置数据源,mybatis的Mapper位置:mybatis: mapper-locations: classpath:mappers/*.xml type-aliases-package: com.example.csdn2 spring: datasource: username: root password: HelloWorld url: jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
然后在需要的service层注入dao层的bean对象,直接使用:
@RunWith(SpringRunner.class) @SpringBootTest public class Test { @Autowired private UserDao userDao; @org.junit.Test public void selectAll() throws Exception{ System.out.println(userDao.selectAll()); } }
-
上述过程中,是在springboot启动的时候,扫描启动类下的包带有@Mapper的接口,生成其代理的bean对象放入spring容器中,当我们需要使用相关dao层的crud的时候,直接注入即可使用。
-
而发现,spring启动帮忙生成的dao层的bean对象已经是跟本项目配置文件中配置的数据源绑定起来了,而我们最开始的目的是将其他数据源也可以使用mybatis的xml方式(sql写入其内),这里固然是无法直接使用spring提供好的dao层bean对象啦,毕竟数据源都来自不一样的内容。
-
所以现在的问题:我们要如何创建dao层接口的代理bean对象呢?
-
mybatis的核心重要类之一:SqlSession,当点进去看该类(接口)的时候,我们惊奇发现mybatis中还可以允许手动创建dao层的bean对象,即在SqlSession接口中有这样子的一个方法:
/** * Retrieves a mapper. * @param <T> the mapper type * @param type Mapper interface class * @return a mapper bound to this SqlSession */ // 根据注释推理: // 1. Mapper interface class 一个接口且是mapper接口 // 2. 返回一个mapper对象且绑定这次的sql连接 <T> T getMapper(Class<T> type);
具体用法如下:
@RunWith(SpringRunner.class) @SpringBootTest public class Test { @Autowired private SqlSession sqlSession; @org.junit.Test public void selectAll() throws Exception{ // 创建dao层的对象 final UserDao userDao = sqlSession.getMapper(UserDao.class); // 使用方法,查询出内容 System.out.println(userDao.selectAll()); } }
(温馨小提示: 这里的内容是涉及mybatis部分内容,若不熟悉可以先自行百度,或者先认为有这个结论) -
这里我们先小结一下,上述主要是讲了:
- 在springboot中一般使用mybatis的方式:知道spring启动会帮我们生成对应dao层的bean对象提供给我们使用。
- mybatis可以允许我们手动创建dao层的bean对象,并且使用。
-
到这里,有的人可能会提问,你怎么知道上述代码中getMapper()方法出来的userDao是新的bean对象,而不是spring容器里面的呢?好问题,来证明一下:
@RunWith(SpringRunner.class) @SpringBootTest public class Test { @Autowired private UserDao userDao; @Autowired private SqlSession sqlSession; @org.junit.Test public void selectAll() throws Exception{ final UserDao userDao = sqlSession.getMapper(UserDao.class); System.out.println("get出来的对象:" + userDao); System.out.println("注入出来的对象:" + this.userDao); } }
-
根据上述使用得知,mybatis可以允许我们手动创建dao层的bean对象,并且使用,但仍然发现,似乎还是使用与本项目配置文件中的数据源,固然还是有问题。这是时候我们知道在spring容器中的SqlSession对象也是用配置文件数据源,那么也就是说明该对象我们无法使用了,得自己创建该对象。
-
现在的问题则抛到了:如何自己创建SqlSession对象呢?
-
当搜索源码的时候,无意中发现SqlSessionFactory类,一看到Factory,这是什么,工厂呀!!!,固然是创建SqlSession的东西,经过一番寻找,发现其方法中有openSession()方法:
-
看我们发现了什么:为一个连接创建 SqlSession:
- 参数是一个连接:我们可以得到,可以输入其他数据库连接,然后返回SqlSession。
- 如果我们得到了SqlSession,岂不是可以直接生成dao层的代理bean对象,卧槽,直接可以使用mapper了。
-
迅速编写代码测试一波:
@RunWith(SpringRunner.class) @SpringBootTest public class Test { @Autowired private SqlSessionFactory sqlSessionFactory; @org.junit.Test public void selectAll() throws Exception{ // 其他数据源 DriverManagerDataSource dataSource = new DriverManagerDataSource(); dataSource.setUrl("jdbc:mysql://localhost:3308/mybatis?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC"); dataSource.setUsername("root"); dataSource.setPassword("root_pwd"); // 创建SqlSession final SqlSession sqlSession = sqlSessionFactory.openSession(dataSource.getConnection()); final UserDao userDao = sqlSession.getMapper(UserDao.class); final List<User> users = userDao.selectAll(); System.out.println(users); } }
-
可恶呀,这里会报错,报错内容是spring的事务要求一个数据源,引起我们思考,我们传入的是一个connection,难道是这里传错内容了?但是根据SqlSessionFactory.openSession()确实可以给个连接?奇怪,完全不懂,没办法了,只能使用绝招,跟源码。
-
当时我看到这里就有点懵了,为啥这里会跟事务扯上关系了呢?难道这个是必须的吗?没办法,后来查阅资源后,得到这一句话:
- mybatis操作的时候跟数据库的每一次连接,都需要创建一个会话,我们用openSession()方法来创建。这个会话里面需要包含一个Executor用来执行 SQL。
- Executor又要指定事务类型和执行器的类型。
-
很好,非常好,看来是无法绕过这个事务了,当我们点击TransactionFactory接口(创建Transaction事务的接口):
可以看到有三种实现类,我们知道上述源码跟踪发现,刚刚抛异常的是 第三个工厂创建出来的springManagedTransaction事务,点击其他类型工厂发现,其他两种不会报错:
-
现在的问题就到了:springboot启动的时候,是如何指定这个事务工厂类型呢,如果我们让它使用其他两种类型,岂不是可以实现为一个连接创建 SqlSession,得到了SqlSession,可以直接生成dao层的代理bean对象,直接可以使用mapper了
-
很遗憾,我翻阅资料发现,如果是使用了springboot整合mybatis的话,那还真没办法,如果是单独使用mybatis就可以,在单独使用mybatis的时候,可以从配置文件入手,这是我觉得比较好的一篇文章:https://blog.csdn.net/niujifei/article/details/125519028。
-
其实到了这里就是有关于springboot整合mybatis的相关知识点了,这里还是不详细说明了,只是看个结果:
-
也就是说 spring自带生成的SqlSessionFactory工厂里面的事务就是用SpringManagedTransaction工厂创建的,固然没办法按照我们一开始预想的实现方案,可恶啊。
-
要放弃了吗?不,既然spring帮我们生成的bean对象无法实现,我们直接自己new 需要的对象出来,给我冲。
3. 结论
-
根据上述思考过程,spring生成的无论是SqlSessionFactoryBean,SqlSessionFactory,SqlSession都无法现实我们需要的,那么我们就自己构建。
@RunWith(SpringRunner.class) @SpringBootTest public class Test { @org.junit.Test public void selectAll() throws Exception{ // 其他数据源 DriverManagerDataSource dataSource = new DriverManagerDataSource(); dataSource.setUrl("jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC"); dataSource.setUsername("root"); dataSource.setPassword("HelloWorld"); // 生成SqlSessionFactoryBean对象,为了创建SqlSessionFactory SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); // 数据源 sqlSessionFactoryBean.setDataSource(dataSource); // 接口对应mapper的路径 sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mappers/*.xml")); // 设置事务工厂类,不使用SpringManagedTransaction,原因思考过程已说 sqlSessionFactoryBean.setTransactionFactory(new JdbcTransactionFactory()); // 获取SqlSessionFactory对象,为了创建SqlSession对象 final SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBean.getObject(); // 创建SqlSession final SqlSession sqlSession = sqlSessionFactory.openSession(dataSource.getConnection()); // 获取mapper对应的dao层接口 final UserDao userDao = sqlSession.getMapper(UserDao.class); final List<User> users = userDao.selectAll(); System.out.println(users); } }
-
我们只要稍微封装一下该方法,就可以作为工具类了,如下:
@RunWith(SpringRunner.class) @SpringBootTest public class Test { @org.junit.Test public void selectAll() throws Exception { // 其他数据源 DriverManagerDataSource dataSource = new DriverManagerDataSource(); dataSource.setUrl("jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC"); dataSource.setUsername("root"); dataSource.setPassword("HelloWorld"); // 获取mapper对应的dao层接口 final UserDao userDao = getMapper(dataSource, UserDao.class); final List<User> users = userDao.selectAll(); System.out.println(users); } private <T> T getMapper(DataSource dataSource, Class<T> clazz) throws Exception { SqlSession sqlSession = createSqlSession(dataSource); return sqlSession.getMapper(clazz); } private SqlSession createSqlSession(DataSource dataSource) throws Exception { // 生成SqlSessionFactoryBean对象,为了创建SqlSessionFactory SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); // 数据源 sqlSessionFactoryBean.setDataSource(dataSource); // 接口对应mapper的路径 sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mappers/*.xml")); // 设置事务工厂类,不使用SpringManagedTransaction,原因思考过程已说 sqlSessionFactoryBean.setTransactionFactory(new JdbcTransactionFactory()); // 获取SqlSessionFactory对象,为了创建SqlSession对象 final SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBean.getObject(); // 创建SqlSession return sqlSessionFactory.openSession(dataSource.getConnection()); } }