整合篇-Spring整合MyBatis

从 0 开始深入学习 Spring 专栏目录总览

OK 整合篇的第二章,我们来整合一下 MyBatis 。首先,我们先来回应一下上一章最后留的问题思考。

SpringFramework 在整合其他框架的时候,最重要的一点,是把其他框架中最核心的部分,交由 SpringFramework 统一管理

所以有了这个思路,我们在整合 MyBatis 的时候也就不会很难。

1. MyBatis整合思路

对于 MyBatis 的原生开发来讲,我们需要的是编写 MyBatis 的配置文件,自行构造 SqlSessionFactory ,并由此生成 SqlSession 后与数据库交互。

除此之外,在具体的模块开发中,我们可以自行编写 Dao 的实现类,也可以借助 Mapper 接口动态代理的方式,将其与 mapper.xml 关联起来。

所以,在整合 SpringFramework 与 MyBatis 时,显然 SqlSessionFactory 和 Mapper 接口 / Dao 实现类,这两部分是最最重要的。

理清楚思路之后,接下来我们就可以开始干活了。

2. 编码整合

接下来我们来开始编码整合。对于 MyBatis 跟 SpringFramework 之间,没有特别的版本要求,所以只需要保证比较新即可。

2.1 添加坐标依赖

小册选择使用 MyBatis 3.5.5 ,以及 MyBatis 与 SpringFramework 的整合包 2.0.5 来进行整合。

    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.5</version>
    </dependency>
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis-spring</artifactId>
        <version>2.0.5</version>
    </dependency>

注意!mybatis-spring 的依赖中,并没有直接依赖 MyBatis ,所以我们需要显式的依赖 MyBatis 的核心包!这一点尤其要注意!

2.2 基本代码准备

下面我们把基本的代码准备一下。

2.2.1 实体类

实体类,我们依然使用 Department 来操作,只不过这次里面干干净净,什么多余的注解也没有:

public class Department {
    
    private Integer id;
    
    private String name;
    
    private String tel;
    
    // ......
}

2.2.2 Dao & Mapper

Dao 实现类和 Mapper 也都比较简单,快速的来编写一下即可:

@Repository
public class DepartmentDao {
    
    @Autowired
    SqlSessionFactory sqlSessionFactory;
    
    public void save(Department department) {
        // 利用try-with-resource简化编码
        try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
            sqlSession.insert("dept.save", department);
        }
    }
    
    public List<Department> findAll() {
        try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
            return sqlSession.selectList("dept.findAll");
        }
    }
}
public interface DepartmentMapper {
    
    void save(Department department);
    
    List<Department> findAll();
}

至于 Dao 实现类中的 SqlSessionFactory 从哪里来,那就是接下来整合的时候,由 SpringFramework 管理,然后注入了。

2.2.3 mapper.xml

为了能同时兼容我们自己声明的方式,以及 Mapper 接口动态代理的方式,这里我们编写两个 mapper.xml 文件,里面的内容基本是一样的,只有 namespace 不一样:

<?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="dept"> <!-- 另一个是com.linkedbear.spring.mybatis.mapper.DepartmentMapper -->

    <insert id="save" parameterType="com.linkedbear.spring.mybatis.entity.Department">
        insert into tbl_department (id, name, tel) values (#{id}, #{name}, #{tel})
    </insert>

    <select id="findAll" resultType="com.linkedbear.spring.mybatis.entity.Department">
        select * from tbl_department
    </select>
</mapper>

另外,与 DepartmentMapper 配合的 mapper.xml 一定要放在与 DepartmentMapper 同包下,后面会用到。

2.2.4 MyBatis全局配置文件

对于 MyBatis 的原生开发来讲,全局配置文件必不可少,那我们可以来编写一个 SqlMapConfig.xml 的配置文件:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <typeAliases>
        <package name="com.linkedbear.spring.mybatis.entity"/>
    </typeAliases>

    <mappers>
        <mapper resource="mybatis/dept.xml"/>
        <mapper resource="mybatis/department.xml"/>
    </mappers>
</configuration>

这里面本来还要配置数据源等等的东西,因为下面我们要整合 SpringFramework ,而数据源必定会在 SpringFramework 中注册好,所以这里没有必要再写了。

2.3 编写配置文件

下面我们就可以编写配置文件,来整合 MyBatis 。这里面有几样东西,咱一个一个来看。

2.3.1 数据源&包扫描&事务

老生常谈的东西,每个项目,每个配置文件几乎都要有的:

<context:component-scan base-package="com.linkedbear.spring.mybatis"/>

<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/spring-dao?characterEncoding=utf8"/>
    <property name="username" value="root"/>
    <property name="password" value="123456"/>
</bean>

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>

<tx:annotation-driven transaction-manager="transactionManager"/>

2.3.2 SqlSessionFacory

最重要的一点,是将 SqlSessionFactory 交给 SpringFramework 来统一管理,而 MyBatis 与 SpringFramework 的整合包中,有一个 FactoryBean ,它就可以来生成 SqlSessionFactory :

整合篇-Spring整合MyBatis

你看这名起的,太直白太浅显易懂了,好,那我们就来注册一个:

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource"/>
    <property name="configLocation" value="classpath:mybatis/SqlMapConfig.xml"/>
</bean>

这里面只需要传入两样东西即可:数据源、MyBatis 全局配置文件

2.4 测试Dao实现类

当注册完 SqlSessionFactory 之后,我们会发现,DepartmentDao 中的注入错误已经消失了,因为此时 SqlSessionFactory 确实有了嘛。那我们就可以来编写测试代码,试一下是不是可以走得通了。

2.4.1 测试代码

@RunWith(SpringRunner.class)
@ContextConfiguration(locations = "classpath:mybatis/spring-mybatis.xml")
public class MyBatisTests {
    
    @Autowired
    DepartmentDao departmentDao;
    
    @Test
    public void testDaoSave() {
        Department department = new Department();
        department.setName("mybatis dao name");
        department.setTel("mybatis dao tel");
        departmentDao.save(department);
    }
    
    @Test
    public void testDaoFindAll() {
        System.out.println(departmentDao.findAll());
    }
}

这测试代码想必各位已经写得很熟练了吧,小册也不细讲了,直接开冲就完事了。

上下依次运行测试代码的方法,可以发现 testDaoSave 确实可以保存数据,testDaoFindAll 方法也可以查出部门信息:

[Department{id=1, name='test department', tel='1122334'}, Department{id=2, name='mybatis dao name', tel='mybatis dao tel'}]

2.4.2 Dao实现类的改进——模板

我们可以发现一点,上面的 Dao 实现类代码中出现了重复代码:

    public void save(Department department) {
        try (SqlSession sqlSession = sqlSessionFactory.openSession()) { // 重复
            sqlSession.insert("dept.save", department);
        }
    }
    
    public List<Department> findAll() {
        try (SqlSession sqlSession = sqlSessionFactory.openSession()) { // 重复
            return sqlSession.selectList("dept.findAll");
        }
    }

这是坏味道,应该想办法去掉才行。关键是,怎么去呢?

MyBatis 跟 SpringFramework 的整合包中,当然少不了模板这个套路啦,所以我们可以在 xml 配置文件中,再注册一个 SqlSessionTemplate :

<bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
    <constructor-arg index="0" ref="sqlSessionFactory"/>
</bean>

注意这里使用了构造器注入,只需要注入 SqlSessionFactory ,模板就可以帮我们构造 SqlSession 。

再注意一点,其实 SqlSessionTemplate 本身也是 SqlSession :

public class SqlSessionTemplate implements SqlSession, DisposableBean

所以它拥有 SqlSession 的所有方法。

这样改造之后,DepartmentDao 就可以使用 SqlSessionTemplate 而不是 SqlSessionFactory 了:

    @Autowired
    SqlSessionTemplate sqlSessionTemplate;
    
    public void save(Department department) {
        sqlSessionTemplate.insert("dept.save", department);
    }
    
    public List<Department> findAll() {
        return sqlSessionTemplate.selectList("dept.findAll");
    }

这样重复代码也就被解决了。

重新运行测试类的测试代码,发现依然可以正常运行,说明一切 OK 。在实际开发中,这种方式相对使用的更多。

2.5 测试Mapper接口动态代理

紧接着,我们来测试 Mapper 接口动态代理的方法,来简化 Dao 开发。

2.5.1 注册Mapper动态代理的Bean

我们写的这个 DepartmentMapper 接口,本身只是一个接口,而 MyBatis 与 SpringFramework 的整合包中,提供了一个可以根据 Mapper 接口生成代理类的MapperFactoryBean ,对没错它又是工厂 Bean 。

使用方式也很简单,在 xml 配置文件中,注册一个 MapperFactoryBean ,并指定对应的 Mapper 接口,传入 SqlSessionFactory 就可以了:

<bean id="departmentMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
    <property name="mapperInterface" value="com.linkedbear.spring.mybatis.mapper.DepartmentMapper"/>
    <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>

有了它,就可以从 IOC 容器中,取出 DepartmentMapper 接口的代理实现类了。

2.5.2 测试代码

好,我们回到测试类中,在这里面注入 DepartmentMapper ,并编写对应的测试方法:

    @Autowired
    DepartmentMapper departmentMapper;

    @Test
    public void testMapperSave() {
        Department department = new Department();
        department.setName("mybatis mapper name");
        department.setTel("mybatis mapper tel");
        departmentMapper.save(department);
    }
    
    @Test
    public void testMapperFindAll() {
        System.out.println(departmentMapper.findAll());
    }

依次运行上下两个方法,均可以成功执行,并查询返回结果:

[Department{id=1, name='test department', tel='1122334'}, Department{id=2, name='mybatis dao name', tel='mybatis dao tel'}, Department{id=3, name='mybatis dao name', tel='mybatis dao tel'}]

2.5.3 Mapper接口的改进——包扫描

试想,如果上面的 Mapper 接口超级多,那我们一个一个的定义,可真是要了老命了。。。所以,我们需要一种别的办法来优化。

一个很简单的办法,就是利用包扫描了。我们完全可以把这些 Mapper 接口以扫描的形式收集起来,然后分别创建对应的 MapperFactoryBean 并生成代理对象,这样就可以解决一个一个定义导致过多的问题了。好,下面我们来配置。

包扫描器,可以选用 MapperScannerConfigurer 来实现,只需要声明要扫描的包,以及 SqlSessionFactory 即可。

<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="basePackage" value="com.linkedbear.spring.mybatis.mapper"/>
    <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>

**注意!!!这里千万不要直接声明 sqlSessionFactory !只能设置 sqlSessionFactoryBeanName !!!**在这里 IDEA 已经给出提示了,这两个属性不能设置:

整合篇-Spring整合MyBatis

究其原因,是因为这个 MapperScannerConfigurer 的本质,是一个 BeanDefinitionRegistryPostProcessor :

public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, 
             InitializingBean, ApplicationContextAware, BeanNameAware

我们在之前的 IOC 章节已经学过了,BeanDefinitionRegistryPostProcessor 的执行阶段是不能随意初始化 Bean 的,可能会引发一些问题,所以这里小伙伴们一定要明白这一点。

另外,在这里声明好 Mapper 扫描之后,也就意味着,以前在 SqlMapConfig.xml 中编写的那个 <mapper> 标签也就没必要写了,因为 SpringFramework 已经帮我们搞定了。

重新运行测试代码,依然可以正常保存、查询,说明这种方法也是完全没有问题的,而且更简便,项目开发中也比较常用这种方式。

2.6 注解驱动方式整合

对于上面的 xml 配置文件的方式来看,相对应的注解配置类,在编写时,方法是一样的,只需要将 xml 中 bean 的声明方式,换为注解配置类的声明方式即可。步骤我们就不拆分了,这里贴一个总的代码,小伙伴们可以对比着来编写和学习。

@Configuration
@ComponentScan("com.linkedbear.spring.mybatis")
@EnableTransactionManagement
public class MyBatisConfiguration {
    
    @Bean
    public DataSource dataSource() {
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:dao/spring-webmvc?characterEncoding=utf8");
        dataSource.setUsername("root");
        dataSource.setPassword("123456");
        return dataSource;
    }
    
    @Bean
    public TransactionManager transactionManager(DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }
    
    @Bean
    public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSource);
        sqlSessionFactoryBean.setConfigLocation(new ClassPathResource("classpath:mybatis/SqlMapConfig.xml"));
        return sqlSessionFactoryBean;
    }
    
    @Bean
    public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
        return new SqlSessionTemplate(sqlSessionFactory);
    }
    
    @Bean
    public MapperFactoryBean<DepartmentMapper> departmentMapper(SqlSessionFactory sqlSessionFactory) {
        MapperFactoryBean<DepartmentMapper> departmentMapperFactoryBean = new MapperFactoryBean<>();
        departmentMapperFactoryBean.setMapperInterface(DepartmentMapper.class);
        departmentMapperFactoryBean.setSqlSessionFactory(sqlSessionFactory);
        return departmentMapperFactoryBean;
    }
    
    @Bean
    public MapperScannerConfigurer mapperScannerConfigurer() {
        MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
        mapperScannerConfigurer.setBasePackage("com.linkedbear.spring.mybatis.mapper");
        mapperScannerConfigurer.setSqlSessionFactoryBeanName("sqlSessionFactory");
        return mapperScannerConfigurer;
    }
}
免责声明:
1.本站所有内容由本站原创、网络转载、消息撰写、网友投稿等几部分组成。
2.本站原创文字内容若未经特别声明,则遵循协议CC3.0共享协议,转载请务必注明原文链接。
3.本站部分来源于网络转载的文章信息是出于传递更多信息之目的,不意味着赞同其观点。
4.本站所有源码与软件均为原作者提供,仅供学习和研究使用。
5.如您对本网站的相关版权有任何异议,或者认为侵犯了您的合法权益,请及时通知我们处理。
火焰兔 » 整合篇-Spring整合MyBatis