Fork me on GitHub

MyBatis框架学习(五)

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
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
<?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>
<!-- 配置全局可以引用的属性值 -->
<!-- 全局设置 -->
<settings>
<setting name="logImpl" value="LOG4J" />
<!-- 针对oracle数据库操作 ,插入不能有null的值的情况, mysql无此问题 -->
<setting name="jdbcTypeForNull" value="NULL" />
<!-- 开启延迟加载 -->
<setting name="lazyLoadingEnabled" value="true" />
<!-- 延迟加载后,哪些方法触发去加载被关联的目标对象,默认有:equals,clone,hashCode,toString -->
<setting name="lazyLoadTriggerMethods" value="clone" />
</settings>
<!-- 配置别名 -->
<typeAliases>
<package name="com.tz.entity" />
<package name="com.tz.dao.vo" />
</typeAliases>
<typeHandlers>
<!-- 针对 OrderStatus 枚举类型的处理,采用下标来存储,而不是枚举字面值 -->
<typeHandler handler="org.apache.ibatis.type.EnumOrdinalTypeHandler"
javaType="com.tz.entity.OrderStatus" />
</typeHandlers>
<!-- 注:连接目标数据库的环境: 此处无需再配置,全部由spring来配置 -->
<!-- 注:添加映射: 使用spring来管理和获取 Mapper, 此处也无需配置 -->
</configuration>

可以看出,相比之前,已经精简了许多。

日志配置文件【可选】

在上面的mybatis-config.xml文件中,设定了以log4j为日志框架,并且我们在pom.xml中,也导入了log4j的依赖,所以,我们只需要在log4j.properties文件中进行相关的配置即可,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
### direct log messages to stdout ###
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n

### set log levels - for more verbose logging change 'info' to 'debug' ###
### all level: FATAL, ERROR, WARN, INFO, DEBUG, TRACE
log4j.rootLogger=warn, stdout
log4j.logger.com.tz.dao=debug
#
log4j.logger.com.tz.service=debug
log4j.logger.org.springframework.jdbc.datasource=debug
log4j.logger.org.aspectj=debug

上面的配置中,我把根日志器的级别调到了warn级, 而针对com.tz.dao和com.tz.service包设定了debug级,这些都是可以自由设定的,按需设定.

持久层Dao和xml文件

这部份代码与单独使用mybatis是一样的,没有任何区别,由于之前的系列教程都已写明操作步骤,此处不再描述,如果对mybatis框架下的dao和xml文件的开发不熟悉,请查看之前的学习文档。

代码略….
如需要看之前的教程,请点击此处看系列教程

业务层Service

这一层代码是之前的教程中没有写的,之前我们直接在测试代码中使用mybatis的API来做CRUD操作获取数据,现在通过DI框架,我们可以在Service接口的实现类中注入目标Dao[动态生成的], 下面是代码和配置

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
package com.tz.service;

import java.io.Serializable;
import java.util.List;
import java.util.Map;

import com.tz.entity.Customer;

/**
* 客户服务接口
* @author yejf
*
*/
public interface CustomerService {

void save(Customer c);

Customer findById(Serializable id);

List<Customer> findAll();

void delete(Serializable id);

void update(Customer c);

List<Customer> findByProperty(Map<String, Object> propMap);

}
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
package com.tz.service.impl;

import java.io.Serializable;
import java.util.List;
import java.util.Map;

import org.apache.log4j.Logger;

import com.tz.dao.CustomerDao;
import com.tz.entity.Customer;
import com.tz.service.CustomerService;

/**
* 实现类
* @author yejf
*
*/
public class CustomerServiceImpl implements CustomerService {

/****
* 利用 spring框架依赖注入
*/
private CustomerDao customerDao;
//日志器,可以不用
private static Logger LOGGER = Logger.getLogger(CustomerServiceImpl.class);

public CustomerDao getCustomerDao() {
return customerDao;
}
public void setCustomerDao(CustomerDao customerDao) {
this.customerDao = customerDao;
}

@Override
public void save(Customer c) {
LOGGER.debug("业务层开始调用dao的save方法...");
customerDao.save(c);
LOGGER.debug("dao的save方法调用结束...");
}

@Override
public Customer findById(Serializable id) {
LOGGER.debug("业务层开始调用dao的findById方法...");
return customerDao.findById(id);
}

@Override
public List<Customer> findAll() {
LOGGER.debug("业务层开始调用dao的findAll方法...");
return customerDao.findAll();
}

@Override
public void delete(Serializable id) {
LOGGER.debug("业务层开始调用dao的delete方法...");
customerDao.delete(id);
LOGGER.debug("dao的delete方法调用结束...");
}

@Override
public void update(Customer c) {
LOGGER.debug("业务层开始调用dao的update方法...");
customerDao.update(c);
LOGGER.debug("dao的update方法调用结束...");
}

@Override
public List<Customer> findByProperty(Map<String, Object> propMap) {
LOGGER.debug("业务层开始调用dao的findByProperty方法...");
return customerDao.findByProperty(propMap);
}
}

在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
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
package com.tz.service;

import org.junit.BeforeClass;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.tz.entity.Customer;

//测试类
public class CustomerServiceTest {

private static CustomerService customerService;

@BeforeClass
public static void init() {
//初始化Spring框架上下文
ApplicationContext ac =
new ClassPathXmlApplicationContext("applicationContext.xml");
//通过Spring API 来获取业务对象
customerService = ac.getBean(CustomerService.class);
}

@Test
public void testFindById() {
Customer c = customerService.findById(2);
System.out.println(c);
}
...
...
}

测试结果如下:

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进行整合。

谢谢!