MyBatis Framework
简介:
MyBatis 框架是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录
本系列笔记可能会遇到的专业词汇有:
- Framework, 框架,某一类问题的总体解决方案
- ORM,
Object Relationship Mapping
, 对象关系映射 - DATABASE, 数据库,存储数据的一种方式
- Mapper,
是一种mybatis做持久层的称谓
, 相当于DAO层,所不同的是Mapper只需要提供接口和对应的xml文件,无需实现 - SqlSession, myBatis会话, 一般做为局部变量进行操作
- SqlSessionFactory, SqlSession的工厂,用来创建SqlSession实例
本系列笔记包含如下的课程内容:
- myBatis入门教程和日志配置
- myBatis框架配置文件和映射文件
- 配置文件详解
- xml映射文件详解
- 注解映射详解
- 关联映射和动态SQL机制
- 缓存机制和API分析
- 一级缓存
- 二级缓存
- 核心API分析
- MyBatis+Spring 整合
- Spring+MyBatis+SpringMVC 整合
MyBatis+Spring 整合
从前面的学习中可以得知,mybatis的好处之一就是我们无需手动编写Dao或Mapper的实现类,而是框架会帮助我们动态生成它们的代理实现类,这样一来,我们就省去了开发实现类的时间,取而代之的是编写xml映射文件,但是,这给我们将来的业务层带来的“挑战”,因为如果在业务层中要调用mybatis框架实现的Dao或Mapper,就会使用mybatis框架的API,这样一来,mybatis框架就侵入了业务层的领地,换句话说,业务层与mybatis框架耦合了,这当然不是我们希望看到的。
解决方案就是通过引入DI框架,也就是依赖注入框架,这类框架目前主流的有Spring和Guice[来自google], 我们这个教程选用使用较广的Spring框架,通过DI,我们就可以让业务层和mybatis框架的Dao或Mapper解耦合,并且动态注入相应的对象,这也是mybatis官方推荐的使用方式,套用一种流行语的说法,与DI框架的整合,才是mybatis框架的正确打开方式。:)
我们的教案采用maven进行管理,这样更方便操作【创建maven项目什么的,这里就不做说明,如果不会的话,你应该还不会看到里:( 】
整个项目所依赖的组件包含如下:
- mybatis
- mybatis-spring
- spring-context
- spring-jdbc
- spring-aspects
- mysql-connector-java [不同的数据库,选用不同的驱动]
- commons-dbcp [可以选择其它的连接池组件]
- log4j [可以选择其它的日志实现]
- junit [可以选择其它的单元测试框架]
pom.xml配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.tz</groupId>
<artifactId>mybatis-spring-demo</artifactId>
<version>1.0</version>
<packaging>jar</packaging>
<name>Maven Java Project</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<!-- 指定JVM编译器版本 -->
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<!-- 指定spring框架版本 -->
<spring.version>4.1.4.RELEASE</spring.version>
</properties>
<dependencies>
<!-- mybatis框架依赖 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.5</version>
</dependency>
<!-- mybatis-spring依赖 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.1</version>
</dependency>
<!-- 日志依赖 -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<!-- spring DI框架依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- spring-jdbc框架依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- spring-aspects依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- apache commons dbcp -->
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.4</version>
</dependency>
<!-- 目标数据库,这里采用mysql -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.40</version>
</dependency>
<!-- 单元测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
<!-- 构建配置 -->
<build>
<plugins>
<!-- config other plugin [可选] -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>2.9</version>
</plugin>
</plugins>
<!--
之所以写这个resources,是因为maven编译机制默认情况下只把 src/main/resources 下的所有资源
编译到target/classes目录下
而我们把mybatis的映射文件 xxx.xml 存放到 src/main/java 下的包中,默认情况下这里的非 .java 文件是不会被编译的,
所以,才需要改变默认的形为,如下:
-->
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.xml</include>
<include>**/*.properties</include>
</includes>
</resource>
</resources>
</build>
</project>
可以直接copy到你自己项目的pom.xml中,当然,命名空间什么的你自己修改一下即可。
上面的配置中都有做相关的注释说明,此处不再描述。
spring配置文件
做为DI容器使用,本身也需要进行相关的配置,主要包含:
- 数据源的配置
- SqlSessionFactoryBean
- 配置Mapper[有两种方式]
- 方式一:单个配置
- 方式二:扫描整个包[推荐]
- 配置Service
- 配置事务管理器
- 配置事务切面
由于我把连接DB的属性写在外面的properties文件中了,文件名为:dbconfig.properties, 内容如下:1
2
3
4
5
6
7
8
9
10# database connection properties configuration
mysql_driver=com.mysql.jdbc.Driver
mysql_url=jdbc:mysql://localhost:3306/mybatisdb?useUnicode=true&characterEncoding=utf-8
mysql_user=root
mysql_pwd=****
# commons properties about pools
initialSize=5
maxActive=2
...
下面就列出applicationContext.xml的配置内容1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 加载配置文件 -->
<context:property-placeholder location="dbconfig.properties"/>
<!-- 配置数据源 -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="${mysql_driver}" />
<property name="url" value="${mysql_url}" />
<property name="username" value="${mysql_user}" />
<property name="password" value="${mysql_pwd}" />
<!-- 初始化池大小 -->
<property name="initialSize" value="${initialSize}" />
<property name="maxActive" value="${maxActive}"></property>
</bean>
<!-- 配置SqlSessionFactoryBean -->
<bean id="sqlSessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 指定mybatis的配置文件 -->
<property name="configLocation" value="mybatis-config.xml"/>
<!-- 指定数据源 -->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置dao,也叫配置Mapper -->
<!-- 配置Mapper:
方式一: 配置单个mapper, 每个dao/mapper 对应的类型都是: org.mybatis.spring.mapper.MapperFactoryBean
-->
<bean id="customerDao" class="org.mybatis.spring.mapper.MapperFactoryBean">
<!-- 指定被代理的接口 -->
<property name="mapperInterface" value="com.tz.dao.CustomerDao" />
<!-- 指定sqlSessionFactory-->
<property name="sqlSessionFactory" ref="sqlSessionFactoryBean"/>
</bean>
<bean id="orderDao" class="org.mybatis.spring.mapper.MapperFactoryBean">
<!-- 指定被代理的接口 -->
<property name="mapperInterface" value="com.tz.dao.OrderDao" />
<!-- 指定sqlSessionFactory-->
<property name="sqlSessionFactory" ref="sqlSessionFactoryBean"/>
</bean>
<!-- 配置事务管理器 -->
<bean id="tm" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 配置Service -->
<bean id="customerService" class="com.tz.service.impl.CustomerServiceImpl">
<!-- 此处引用 customerDao 的名字必需要接口名一致,因为上面没有显示地配置xxxDao,而是通过扫描自动生成的 -->
<!-- <constructor-arg name="customerDao" ref="customerDao" /> -->
<property name="customerDao" ref="customerDao" />
</bean>
<!-- 使用内置的事务Advice -->
<tx:advice id="txAdvice" transaction-manager="tm">
<tx:attributes>
<tx:method name="*" propagation="REQUIRED" isolation="READ_COMMITTED"/>
</tx:attributes>
</tx:advice>
<!-- 配置申明式事务切面 -->
<!--
此处如果配置切面不正确,会抛出异常:Could not obtain transaction-synchronized Session for current thread
出错原因就是当前调用的方法没有配置到声明式事务中去导致的.
-->
<aop:config>
<aop:advisor advice-ref="txAdvice" pointcut="execution(* com.tz.service.impl.*ServiceImpl.*(..))"/>
</aop:config>
</beans>
代理单个Dao或Mapper方式
这种方式每个DAO的
元素中的类型属性class的值,都是 org.mybatis.spring.mapper.MapperFactoryBean
,因为 mybatis框架中无需开发者编写DAO的实现类,而是采用动态代理生成实现类
下面把代理单个Dao或Mapper 方式的配置片断再单独贴出来:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16<!-- 配置dao,也叫配置Mapper -->
<!-- 配置Mapper:
方式一: 配置单个mapper, 每个dao/mapper 对应的类型都是: org.mybatis.spring.mapper.MapperFactoryBean
-->
<bean id="customerDao" class="org.mybatis.spring.mapper.MapperFactoryBean">
<!-- 指定被代理的接口 -->
<property name="mapperInterface" value="com.tz.dao.CustomerDao" />
<!-- 指定sqlSessionFactory-->
<property name="sqlSessionFactory" ref="sqlSessionFactoryBean"/>
</bean>
<bean id="orderDao" class="org.mybatis.spring.mapper.MapperFactoryBean">
<!-- 指定被代理的接口 -->
<property name="mapperInterface" value="com.tz.dao.OrderDao" />
<!-- 指定sqlSessionFactory-->
<property name="sqlSessionFactory" ref="sqlSessionFactoryBean"/>
</bean>
可以看出,这种配置方式有较多的冗余配置,所以,应该采用如下更好的配置方式
代理整个Dao或Mapper包的方式[推荐]
这种方式可以扫描整个指定包中的所有Dao和Mapper接口,生成动态代理实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!-- 配置Mapper:
式方二:
利用 MapperScannerConfigurer 来扫描指定mapper或dao 包下的所有接口
-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!-- 指定扫描的包名
如果扫描多个包,使用半角逗号隔开.
-->
<property name="basePackage" value="com.tz.dao"></property>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactoryBean"></property>
</bean>
<!-- 在Service中引用Dao -->
<!-- 配置Service -->
<bean id="customerService" class="com.tz.service.impl.CustomerServiceImpl">
<!-- 此处引用 customerDao 的名字必需要接口名一致,因为上面没有显示地配置xxxDao,而是通过扫描自动生成的 -->
<property name="customerDao" ref="customerDao" />
</bean>
建议采用上面的配置方式
mybatis配置文件
由于连接池相关的属性和Mapper映射都由Spring的DI托管了,所以,在mybatis的配置文件中,这两部份的配置都可以去掉了,如下:
1 | <?xml version="1.0" encoding="UTF-8"?> |
可以看出,相比之前,已经精简了许多。
日志配置文件【可选】
在上面的mybatis-config.xml文件中,设定了以log4j为日志框架,并且我们在pom.xml中,也导入了log4j的依赖,所以,我们只需要在log4j.properties文件中进行相关的配置即可,如下:
1 | ### direct log messages to stdout ### |
上面的配置中,我把根日志器的级别调到了warn级, 而针对com.tz.dao和com.tz.service包设定了debug级,这些都是可以自由设定的,按需设定.
持久层Dao和xml文件
这部份代码与单独使用mybatis是一样的,没有任何区别,由于之前的系列教程都已写明操作步骤,此处不再描述,如果对mybatis框架下的dao和xml文件的开发不熟悉,请查看之前的学习文档。
代码略….
如需要看之前的教程,请点击此处看系列教程
业务层Service
这一层代码是之前的教程中没有写的,之前我们直接在测试代码中使用mybatis的API来做CRUD操作获取数据,现在通过DI框架,我们可以在Service接口的实现类中注入目标Dao[动态生成的], 下面是代码和配置
1 | package com.tz.service; |
1 | package com.tz.service.impl; |
在applicationContext.xml中的配置,我们再看一下:1
2
3
4<!-- 配置Service -->
<bean id="customerService" class="com.tz.service.impl.CustomerServiceImpl">
<property name="customerDao" ref="customerDao" />
</bean>
这样一业,DI容器会把DAO的实现注入到Service的实现中,真正做到业务层与mybatis框架解耦合.
测试代码
在实际开发中,Dao也是要测试的,本案例算是偷了个懒,直接测试Service,如下:
1 | package com.tz.service; |
测试结果如下:
13:43:15,633 DEBUG CustomerServiceImpl:57 - 业务层开始调用dao的findById方法…
13:43:15,649 DEBUG findById:159 - ==> Preparing: select c.id as cid, c.name, c.loc, c.phone, c.c_level ,o.* from t_customer c left join t_order o on c.id = o.customer_id where c.id = ?
13:43:15,705 DEBUG findById:159 - ==> Parameters: 2(Integer)
13:43:15,725 DEBUG findById:159 - <== Total: 2
Customer [id=2, name=史大哈, location=苏州市烽火路石路老街, mobilePhone=15890776688, level=MIDDLE]
总结
mybatis+spring的整合,大大提高了mybatis开发效率,同时,向上对Service提供服务的方式也更为便捷,通过DI容器,让业务层与mybatis框架开发的持久层耦,达到了软件分层设计的要求。
当然,我们的这个案例采用xml的配置方式,对于spring框架来说,也支持注解的配置方式,在理解了xml的基础之上,改成注解方式也是非常容易的。
希望通过这个教程,开发者可以step-by-step进行整合。
谢谢!