spring boot mybatis 整合_我的Java Web之路57 - Spring整合ORM(MyBatis)

本系列文章旨在记录和总结自己在Java Web开发之路上的知识点、经验、问题和思考,希望能帮助更多(Java)码农和想成为(Java)码农的人。

目录

  1. 介绍
  2. 添加 MyBatis-Spring 的依赖
  3. 构造SqlSessionFactory对象
  4. 构造SqlSessionTemplate对象
  5. 将SqlSessionTemplate对象注入到其他组件
  6. 使用SqlSessionTemplate对象访问数据库
  7. 验证
  8. 去除获取Mapper对象的代码
  9. 总结

介绍

这篇文章中我们初步使用了MyBatis,虽然带来如下好处:

  • 将SQL代码从我们的Java源码中分离出去;
  • 帮我们完成对象-关系的映射(方法-SQL语句、方法参数-SQL参数、方法返回值-SQL结果集)

却又再次出现了我们直接使用JDBC接口的问题(参考这篇文章),即出现了重复代码:

  • 每次访问数据库都需要获取SqlSession对象:
try (SqlSession sqlSession = sqlSessionFactory.openSession()) { //访问数据库}
  • 每次访问数据库都需要获取相应的Mapper对象:
HouseMapper houseMapper = sqlSession.getMapper(HouseMapper.class);

如果出现了这种重复,要么考虑自己设计方案消除重复;要么你就要寻找第三方框架。为节省时间成本 ,显然是先寻找第三方框架比较划算。

而众所周知,Spring的终极目标就是简化Java开发,可以说它是为简化开发而生的(可以参考这篇文章)。Spring博大精深,旗下有众多子项目或者模块,相信大多数简化开发方面的问题都可以找到相应的整合/集成方案。

比如,针对JDBC,就有Spring-JDBC;针对ORM,就有Spring-ORM;ORM框架有Hibernate、JPA等,Spring也有Spring-Hibernate、Spring-JPA等模块,大家可以参考其官方文档 (https://docs.spring.io/spring/docs/5.2.2.RELEASE/spring-framework-reference/data-access.html#orm)学习这些模块的使用。

086afedfb329b312894a5ecb2d8518a1.png

那么Spring肯定也应该有Spring-MyBatis模块才对,结果却是出人意料,Spring并没有Spring-MyBatis模块。

不过幸运的是,MyBatis为我们提供了一个与Spring整合的项目/模块,该项目的名字就是 MyBatis-Spring。 Spring-JDBC有一个JdbcTemplate(参考这篇文章),而MyBatis-Spring也有一个类似的SqlSessionTemplate,我们今天就使用它来消除上述的重复代码。

添加 MyBatis-Spring 的依赖

因为我们使用Maven来管理我们的项目依赖,所以我们可以直接到Maven中央仓库去寻找MyBatis-Spring 的依赖配置(参考这篇文章),当然,你也可以手动去下载。

3f854280f3ece63b8c8be379612927fd.png

可以看到,MyBatis-Spring项目的官网与MyBatis的官网用同一个父域名:
http://www.mybatis.org 。

所以,在我们租房网应用的POM文件中添加如下配置即可:

org.mybatis mybatis-spring 2.0.3

构造SqlSessionFactory对象

我们仍然需要一个能够不断生产SqlSession的工厂,即SqlSessionFactory。不过,我们之前已经采用Spring IoC基于Java的方式配置过它(参考这篇文章):

package houserenter.config;import java.io.IOException;import java.io.InputStream;import org.apache.ibatis.io.Resources;import org.apache.ibatis.session.SqlSessionFactory;import org.apache.ibatis.session.SqlSessionFactoryBuilder;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;@Configurationpublic class MybatisConfig {@Beanpublic SqlSessionFactory sqlSessionFactory() throws IOException {String resource = "mybatis-config.xml";InputStream inputStream = Resources.getResourceAsStream(resource);return new SqlSessionFactoryBuilder().build(inputStream);}}

所以,我们直接使用这个配置即可。

构造SqlSessionTemplate对象

与JdbcTemplate的使用类似,我们也要构造一个SqlSessionTemplate对象,这样在应用运行期间各个组件就可以使用该对象来访问数据库。

事实上,SqlSessionTemplate和JdbcTemplate还有其他相似之处,比如都是线程安全的,即一旦构造出对象之后,该对象可以被其他组件所共享使用。

又比如,构造方式也是相似的,我们也是借助Spring IoC来构造对象,可以基于Java配置,也可以基于XML配置。

如果采用基于Java配置,可以参考这篇文章(直接在上面的 MybatisConfig 类中添加配置看起似乎更好一点,因为都属于MyBatis配置的一方面):

@Beanpublic SqlSessionTemplate sqlSession() throws IOException {return new SqlSessionTemplate(sqlSessionFactory());}

如果你要问我如何得知SqlSessionTemplate的构造函数的,答案显而易见,要么观其源码,要么观其JavaDoc。

如果采用基于XML配置,可以参考这篇文章,在租房网项目的dispatcher.xml添加如下配置(我也把JdbcTemplate的配置写上,便于比较,实际是不需要的):

<?xml version="1.0" encoding="UTF-8"?>

值得注意的是,基于Java配置的方法名和基于XML配置的id,都是 sqlSession ,而非 sqlSessionTemplate 。为什么呢?因为该对象虽然具体类型是SqlSessionTemplate,但这个类也实现了SqlSession接口,所以也可以作为一个SqlSession对象来使用。不过,这个接口实现仅仅是为了包装成SqlSession的样子,实际上SqlSessionTemplate内部是包含了一个真正的SqlSession对象。这就是设计模式中的包装器(Wrapper)模式。而这个真正的SqlSession对象实际上又是一个使用动态代理技术生成的对象。

闲话少絮,后面要注入该对象的时候,也是使用这个名字,而类型也是SqlSession。

将SqlSessionTemplate对象注入到其他组件

直接使用Spring IoC的基于注解的自动装配方式,在我们的HouseService类中注入SqlSessionTemplate对象(也是SqlSession对象):

@Autowiredprivate SqlSession sqlSession;

注意,变量名一定要与上面的配置保持一致。

使用SqlSessionTemplate对象访问数据库

实际上,原来访问数据库的代码可以不用修改,只需要把如下的部分删除即可:

try (SqlSession sqlSession = sqlSessionFactory.openSession()) {}

当然,原来注入SqlSessionFactory对象的代码也可以删除了。

现在,我们的HouseService类变成了这样:

package houserenter.service;import java.io.IOException;import java.util.List;import javax.annotation.PostConstruct;import org.apache.ibatis.session.SqlSession;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import houserenter.entity.House;import houserenter.mapper.HouseMapper;@Servicepublic class HouseService {@Autowiredprivate SqlSession sqlSession;@PostConstructpublic void generateMockHouses() throws IOException {HouseMapper houseMapper = sqlSession.getMapper(HouseMapper.class);houseMapper.dropTableIfExistsHouse();houseMapper.cteateTable();houseMapper.insert(new House("1", "金科嘉苑3-2-1201", "详细信息"));houseMapper.insert(new House("2", "万科橙9-1-501", "详细信息"));sqlSession.commit();}public List findHousesInterested(String userName) {HouseMapper houseMapper = sqlSession.getMapper(HouseMapper.class);return houseMapper.selectAll();}public House findHouseById(String houseId) {HouseMapper houseMapper = sqlSession.getMapper(HouseMapper.class);return houseMapper.selectById(houseId);}public void updateHouseById(House house) {HouseMapper houseMapper = sqlSession.getMapper(HouseMapper.class);houseMapper.updateById(house);sqlSession.commit();}}

没有了try-catch块,我们的代码是不是又清爽了不少:)

验证

现在,我们就可以进行验证了,其他部分都不用做任何修改,包括配置Druid连接池的DruidDataSourceFactory,MyBatis配置元数据mybatis-config.xml、Mapper元数据HouseMapper.xml、Mapper接口HouseMapper等。

然而,令人遗憾的是出现了如下异常:

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'houseRenterController': Unsatisfied dependency expressed through field 'houseService'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'houseService': Invocation of init method failed; nested exception is java.lang.UnsupportedOperationException: Manual commit is not allowed over a Spring managed SqlSession

该异常最后的信息似乎是说SqlSession对象已经交由Spring来管理,所以不允许手动提交(事务)。那我们就把最后的:

sqlSession.commit();

这句代码删除或注释,再试试。哈哈,真的解决了!

去除获取Mapper对象的代码

现在HouseService类中的每个方法还需要先获取HouseMapper对象,才能调用相关的访问数据库的方法,能不能把这部分代码也去除呢?

答案是可以。第一种方式是使用SqlSession的如下接口:

// T selectOne(String statement) List selectList(String statement) Cursor selectCursor(String statement) Map selectMap(String statement, String mapKey)int insert(String statement)int update(String statement)int delete(String statement)

所以,我们的HouseService如果采用这种方式的话,就变成这样:

package houserenter.service;import java.io.IOException;import java.util.List;import javax.annotation.PostConstruct;import org.apache.ibatis.session.SqlSession;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import houserenter.entity.House;@Servicepublic class HouseService {@Autowiredprivate SqlSession sqlSession;@PostConstructpublic void generateMockHouses() throws IOException {sqlSession.update("houserenter.mapper.HouseMapper.dropTableIfExistsHouse");sqlSession.update("houserenter.mapper.HouseMapper.cteateTable");sqlSession.update("houserenter.mapper.HouseMapper.insert", new House("1", "金科嘉苑3-2-1201", "详细信息"));sqlSession.update("houserenter.mapper.HouseMapper.insert", new House("2", "万科橙9-1-501", "详细信息"));}public List findHousesInterested(String userName) {return sqlSession.selectList("houserenter.mapper.HouseMapper.selectAll");}public House findHouseById(String houseId) {return sqlSession.selectOne("houserenter.mapper.HouseMapper.selectById", houseId);}public void updateHouseById(House house) {sqlSession.update("houserenter.mapper.HouseMapper.updateById", house);}}

现在我们可以验证一下,倒也没什么问题,但是,这种方式有一个缺点,就是谁能保证HouseMapper的方法的全限定名称就一定不会写错呢,一旦写错了,Java编译器也没法判断出来啊,所以,这种方式实在是不可取。

你也许会说我们可以仍然使用Spring IoC基于Java配置构造出一个全局的HouseMapper对象(而不是构造SqlSessionTemplate对象),然后注入到HouseService组件中啊,代码如下。

MybatisConfig.java:

package houserenter.config;import java.io.IOException;import java.io.InputStream;import org.apache.ibatis.io.Resources;import org.apache.ibatis.session.SqlSessionFactory;import org.apache.ibatis.session.SqlSessionFactoryBuilder;import org.mybatis.spring.SqlSessionTemplate;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import houserenter.mapper.HouseMapper;@Configurationpublic class MybatisConfig {@Beanpublic SqlSessionFactory sqlSessionFactory() throws IOException {String resource = "mybatis-config.xml";InputStream inputStream = Resources.getResourceAsStream(resource);return new SqlSessionFactoryBuilder().build(inputStream);}@Beanpublic HouseMapper houseMapper() throws Exception {SqlSessionTemplate sqlSessionTemplate = new SqlSessionTemplate(sqlSessionFactory());return sqlSessionTemplate.getMapper(HouseMapper.class);}}

HouseService.java:

package houserenter.service;import java.io.IOException;import java.util.List;import javax.annotation.PostConstruct;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import houserenter.entity.House;import houserenter.mapper.HouseMapper;@Servicepublic class HouseService {@Autowiredprivate HouseMapper houseMapper;@PostConstructpublic void generateMockHouses() throws IOException {houseMapper.dropTableIfExistsHouse();houseMapper.cteateTable();houseMapper.insert(new House("1", "金科嘉苑3-2-1201", "详细信息"));houseMapper.insert(new House("2", "万科橙9-1-501", "详细信息"));}public List findHousesInterested(String userName) {return houseMapper.selectAll();}public House findHouseById(String houseId) {return houseMapper.selectById(houseId);}public void updateHouseById(House house) {houseMapper.updateById(house);}} 

现在,我们的HouseService类更加简洁了,每个方法中只剩下了调用HouseMapper的代码。

经过简单验证了一下,也没有问题。

是不是这样就大功告成了呢?不知道大家有么有想过一个问题,就是这种方式注入HouseService组件的HouseMapper对象是线程安全的吗?

有人说,我们的HouseMapper对象是通过线程安全的SqlSessionTemplate的getMapper()方法得到的,所以应该是安全的啊。我倒是觉得未必。不过MyBatis-Spring官网也介绍过这种方式注入Mapper对象,当然也介绍了使用MapperFactoryBean这个组件的方式。当然,大家完全可以自行测试一下,看此种方式获取的HouseMapper对象是否线程安全。

我想说的是,大家一定要有怀疑精神,即便MyBatis-Spring官网介绍过此种方式,但MyBatis介绍说Mapper对象的生命周期最好是方法级别的(因为原来没有不是用SqlSessionTemplate的getMapper()方法,而是DefaultSqlSession的getMapper()方法)。

总结

综上所述,使用SqlSessionTemplate跟使用JdbcTemplate还是相当类似的:

8c90e4fd43a30e258fe0f0909b0e65e9.png

这个图与JdbcTemplate的使用图类似(可以参考这篇文章):

  • SqlSessionTemplate依赖于SqlSessionFactory接口及其实现类,当然,SqlSessionFactory肯定也是使用的配置好的DataSource;
  • JdbcTemplate依赖于DataSource接口及其实现类。

虽然使用SqlSessionTemplate之后,我们的代码已经相当简洁了,但是MyBatis-Spring官网推荐的是使用MapperFactoryBean构造Mapper对象的方式;另一方面,也推荐使用SqlSessionFactoryBean构造SqlSessionFactory对象的方式。我们后续讨论使用这两个组件的方式。

所以,Spring集成MyBatis的基本步骤就是:

  • 添加MyBatis-Spring项目的依赖;
  • Spring IoC中配置SqlSessionFactory组件;
  • Spring IoC中配置SqlSessionTemplate组件;或者配置某个具体的Mapper组件;
  • 使用SqlSessionTemplate或某个具体的Mapper即可。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值