Spring + MyBatis
1、准备
1.1、添加依赖
- 父模块 ( parent )
必须先在 父模块 的 <dependencyManagement> 内部的 <dependencies> 中 添加依赖,子模块才可以继承
- 当前模块( integration )
<dependencies>
<!-- MySQL Driver -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- Oracle Driver -->
<dependency>
<groupId>com.oracle</groupId>
<artifactId>oracle-thin</artifactId>
</dependency>
<!-- 阿里巴巴提供的数据源组件 ( Druid ) -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
</dependency>
<!-- Spring Context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<!-- Spring JDBC -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
</dependency>
<!-- Spring Test -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
</dependency>
<!-- JUnit 单元测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
<!-- AspectJ 是 Spring AOP 的支持包 -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
<!-- MyBatis 核心包 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
</dependency>
<!-- MyBatis 为 整合 Spring 提供的 支持包 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
</dependency>
</dependencies>
1.2、创建实体类
package com.itlaobing.integration.entity;
import java.io.Serializable;
import java.util.Date;
public class Customer implements Serializable {
private Integer id ; // Object Identifier Field
private String username ; // 登录名称
private String password ; // 登录密码
private String name ; // 姓名
private char gender ; // 性别
private Date birthdate ; // 出生年月
}
1.3、创建表
- MySQL
-- 如果 t_customers 表已经存在则删除它
DROP TABLE IF EXISTS t_customers ;
-- 创建表
CREATE TABLE t_customers (
id INT(5) ,
username VARCHAR(20) UNIQUE NOT NULL ,
password VARCHAR(32) NOT NULL ,
name VARCHAR(60) ,
gender CHAR(1) ,
birthdate date ,
PRIMARY KEY( id )
);
INSERT INTO t_customers ( id , username , password )
VALUES ( 1 , 'zhangsanfeng' , 'hello' ) , ( 2 , 'zhangcuishan' , 'hello' ) , ( 3 , 'yinsusu' , 'hello' ) ;
- Oracle
CREATE TABLE t_customers (
id NUMBER(5) ,
username VARCHAR2(20) UNIQUE NOT NULL ,
password VARCHAR2(32) NOT NULL ,
name VARCHAR2(60) ,
gender CHAR(1) ,
birthdate date ,
PRIMARY KEY( id )
);
TRUNCATE TABLE t_customers ;
INSERT ALL
INTO t_customers ( id , username , password ) VALUES ( 1 , 'zhangsanfeng' , 'hello' )
INTO t_customers ( id , username , password , name ) VALUES ( 2 , 'zhangcuishan' , 'hello' , '张翠山' )
INTO t_customers ( id , username , password , name ) VALUES ( 3 , 'yinsusu' , 'hello' , '殷素素' )
SELECT * FROM dual ;
1.4、提供属性文件 ( db.properties )
在 类路径下的 com/itlaobing/integration 目录下 提供 db.properties
jdbc.connection.driver = oracle.jdbc.driver.OracleDriver
jdbc.connection.url = jdbc:oracle:thin:@localhost:1521:ycpower
jdbc.connection.username = itlaobing
jdbc.connection.password =itlaobing
druid.filters = stat
druid.maxActive =20
druid.initialSize = 1
druid.maxWait = 60000
druid.minIdle = 10
druid.maxIdle = 15
druid.timeBetweenEvictionRunsMillis = 60000
druid.minEvictableIdleTimeMillis = 300000
druid.validationQuery = SELECT 'x' FROM dual
druid.testWhileIdle = true
druid.testOnBorrow = false
druid.testOnReturn = false
druid.maxOpenPreparedStatements = 20
druid.removeAbandoned = true
druid.removeAbandonedTimeout = 1800
druid.logAbandoned = true
1.5、提供 Spring 配置文件 ( beans.xml )
在 类路径下的 com/itlaobing/integration 目录下 提供 beans.xml
<?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:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
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">
</beans>
2、数据源
2.1、读取 db.properties 文件
在 com/itlaobing/integration/beans.xml 文件中添加以下内容:
<!-- 指定 属性文件的 位置 -->
<context:property-placeholder location="classpath*:com/**/integration/db.properties" />
2.2、声明 DataSource 对应的 bean
可以通过 Spring 的 JDBC 模块中的 DriverManagerDataSource 来创建数据源。
也可以借助于第三方的实现,比如 DBCP 、C3P0 、Druid 等。这里选择使用 阿里巴巴 的 Druid 。
在 com/itlaobing/integration/beans.xml 文件中添加以下内容:
<!-- 配置 DruidDataSource -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.connection.driver}" />
<property name="url" value="${jdbc.connection.url}" />
<property name="username" value="${jdbc.connection.username}" />
<property name="password" value="${jdbc.connection.password}" />
<property name="filters" value="${druid.filters}" />
<!-- 最大并发连接数 -->
<property name="maxActive" value="${druid.maxActive}" />
<!-- 初始化连接数量 -->
<property name="initialSize" value="${druid.initialSize}" />
<!-- 配置获取连接等待超时的时间 -->
<property name="maxWait" value="${druid.maxWait}" />
<!-- 最小空闲连接数 -->
<property name="minIdle" value="${druid.minIdle}" />
<!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
<property name="timeBetweenEvictionRunsMillis" value="${druid.timeBetweenEvictionRunsMillis}" />
<!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
<property name="minEvictableIdleTimeMillis" value="${druid.minEvictableIdleTimeMillis}" />
<property name="validationQuery" value="${druid.validationQuery}" />
<property name="testWhileIdle" value="${druid.testWhileIdle}" />
<property name="testOnBorrow" value="${druid.testOnBorrow}" />
<property name="testOnReturn" value="${druid.testOnReturn}" />
<property name="maxOpenPreparedStatements" value="${druid.maxOpenPreparedStatements}" />
<!-- 1800 秒,也就是 30 分钟 -->
<property name="removeAbandonedTimeout" value="${druid.removeAbandonedTimeout}" />
<!-- 关闭 abanded 连接时输出错误日志 -->
<property name="logAbandoned" value="${druid.logAbandoned}" />
</bean>
3、MyBatis
3.1、提供数据访问接口
package com.itlaobing.integration.mapper;
public interface CustomerMapper {
}
3.2、提供 Mapper
就是 提供 数据访问接口 所对应的 Mapper.xml 文件。
在类路径下的 com/itlaobing/integration/mapper 中创建 CustomerMapper.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 的值 必须跟 将要实现的接口的 全限定类名一致 -->
<mapper namespace="com.itlaobing.integration.mapper.CustomerMapper">
</mapper>
MyBatis 内部 通过 解析 CustomerMapper.xml 文件来动态产生一个 CustomerMapper 接口的实现类,并实现其中所有的抽象方法。
3.3、添加 find 方法
3.3.1、提供 TypeHandler
为了解决 从数据库中获取 到 的 类型 跟 实体类字段 类型 之间的转换问题,所以需要定义 TypeHandler 。
- 解决 String 和 char 之间的转换
package com.itlaobing.integration.handler;
import org.apache.ibatis.type.*;
import java.sql.*;
public class GenderHandler implements TypeHandler<Character> {
@Override
public void setParameter(PreparedStatement ps, int patamIndex, Character character, JdbcType jdbcType) throws SQLException {
String gender = null ;
if( character != null ) {
char ch = character.charValue();
switch ( ch ){
case '女' : gender = "F" ; break;
case '男' : gender = "M" ; break;
}
}
ps.setString( patamIndex , gender );
}
@Override
public Character getResult(ResultSet rs, String label ) throws SQLException {
Character ch = null ; // Character 是 char 类型的包装类
String gender = rs.getString( label );
if( gender != null && gender.trim().length() > 0 ) {
char c = gender.trim().charAt( 0 ); // 从 gender 对应的字符串中剔除 首尾空白后 获取 第一个字符
ch = ( c == 'F' ? '女' : ( c == 'M' ? '男' : '\u0000' ) );
}
return ch;
}
@Override
public Character getResult(ResultSet rs, int index) throws SQLException {
Character ch = null ;
String gender = rs.getString( index );
if( gender != null && gender.trim().length() > 0 ) {
char c = gender.trim().charAt( 0 );
ch = ( c == 'F' ? '女' : ( c == 'M' ? '男' : '\u0000' ) );
}
return ch;
}
@Override
public Character getResult(CallableStatement cs, int index) throws SQLException {
Character ch = null ;
String gender = cs.getString( index );
if( gender != null && gender.trim().length() > 0 ) {
char c = gender.trim().charAt( 0 );
ch = ( c == 'F' ? '女' : ( c == 'M' ? '男' : '\u0000' ) );
}
return ch;
}
}
- 解决 java.sql.Date 和 java.util.Date 之间的转换
package com.itlaobing.integration.handler;
import org.apache.ibatis.type.*;
import java.sql.*;
public class DateHandler implements TypeHandler<java.util.Date> {
@Override
public void setParameter(PreparedStatement ps , int paramIndex , java.util.Date date, JdbcType jdbcType) throws SQLException {
java.sql.Date d = null ;
if( date != null ) {
d = new java.sql.Date( date.getTime() );
}
ps.setDate( paramIndex , d );
}
@Override
public java.util.Date getResult(ResultSet rs , String label ) throws SQLException {
return rs.getDate( label );
}
@Override
public java.util.Date getResult(ResultSet rs , int index ) throws SQLException {
return rs.getDate( index );
}
@Override
public java.util.Date getResult(CallableStatement cs, int index ) throws SQLException {
return cs.getDate( index ) ;
}
}
3.3.2、在 数据访问接口中 声明 抽象方法
package com.itlaobing.integration.mapper;
import com.itlaobing.integration.entity.Customer;
public interface CustomerMapper {
Customer find( Integer id ) ; // 需要在 CustomerMapper.xml 中提供 "实现"
}
3.3.3、在 Mapper 文件中提供 对 抽象方法的 “实现”
在 com/itlaobing/integration/mapper/CustomerMapper.xml 文件中追加以下内容:
<!-- 用来将 结果集中的 某一行的 每个列 映射到 一个对象中 -->
<resultMap id="customerResultMap" type="com.itlaobing.integration.entity.Customer" >
<id column="id" property="id" />
<result column="username" property="username" />
<result column="password" property="password" />
<result column="name" property="name" />
<result column="gender" typeHandler="com.itlaobing.integration.handler.GenderHandler" property="gender" />
<result column="birthdate" typeHandler="com.itlaobing.integration.handler.DateHandler" property="birthdate" />
</resultMap>
<!-- CustomerMapper 接口中声明的抽象方法为: Customer find( Integer id ) ; -->
<!-- 【强制】 select 的 id 必须 跟 Mapper 接口中的方法名保持一致 -->
<select id="find" parameterType="int" resultMap="customerResultMap">
SELECT id , username , password , name , gender , birthdate FROM t_customers WHERE id = #{id}
</select>
3.4、添加 update 方法
3.4.1、在 数据访问接口 中声明抽象方法
package com.itlaobing.integration.mapper;
import com.itlaobing.integration.entity.Customer;
import org.springframework.stereotype.Repository;
public interface CustomerMapper {
Customer find( Integer id ) ;
int update( Customer c ) ;
}
3.4.2、在 Mapper 文件中提供 对 抽象方法的 “实现”
注意,这里所使用的 typeHandler 是 3.3.1 中已经创建好的。
<!-- CustomerMapper 接口中声明的抽象方法为: int update( Customer c ) ; -->
<update id="update" parameterType="com.itlaobing.integration.entity.Customer" >
UPDATE t_customers
SET
username = #{username} ,
password=#{password} ,
name = #{name} ,
gender = #{gender,typeHandler=com.itlaobing.integration.handler.GenderHandler} ,
birthdate = #{birthdate,typeHandler=com.itlaobing.integration.handler.DateHandler}
WHERE id = #{id}
</update>
4、整合
4.1、通过 FactoryBean 方式来创建 SqlSessionFactory
注意,通过 FactoryBean 方式来创建 bean ,参见 IoC 部分的举例: DateFactoryBean 。
在 com/itlaobing/integration/beans.xml 文件中添加以下内容:
<!-- 通过配置 SqlSessionFactoryBean 来创建 sqlSessionFactory -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!--指定连接哪个数据库-->
<property name="dataSource" ref="dataSource" />
<!--确定mapper文件所在的路径-->
<property name="mapperLocations" value="classpath*:com/**/mapper/*Mapper.xml" />
</bean>
这里 通过为 SqlSessionFactoryBean 注入 dataSource 属性来解决数据库连接问题。
通过 为 SqlSessionFactoryBean 注入 mapperLocations 属性来确定 Mapper 文件的位置。
将来由 SqlSessionFactoryBean 所创建的 SqlSessionFactory 实例就明确需要连接那个数据库、需要解析那些 Mapper 文件。
4.2、提供 MapperScannerConfigurer
在 com/itlaobing/integration/beans.xml 文件中添加以下内容:
<bean id="mapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!-- 指定 sqlSessionFactory 对应的 bean 的名字 -->
<property name="sqlSessionFactoryBeanName">
<idref bean="sqlSessionFactory" />
</property>
<!-- 指定 数据访问接口 ( Mapper 接口 ) 所在的 包 -->
<property name="basePackage" value="com.itlaobing.integration.mapper" />
</bean>
4.3、在 数据访问接口 上 添加 @Repository 注解
package com.itlaobing.integration.mapper;
import com.itlaobing.integration.entity.Customer;
import org.springframework.stereotype.Repository;
@Repository
public interface CustomerMapper {
Customer find( Integer id ) ;
int update( Customer c ) ;
}
为了将来能够通过:
container.getBean( "customerMapper" , CustomerMapper.class )
来获取一个 CustomerMapper 实例,所以这里使用 @Repository 标注 CustomerMapper 接口。
但是一定要注意,CustomerMapper 是个接口,不能直接实例化。
通过 @Repository 获得一个bean,但凡拿到的实例都是CustomerMapper接口实现类的实例
注:接口只能用@Repository标注
得到的实例为动态代理类(代理相应接口),并实现了相应接口,由Mybatis创建
4.4、扫描组件
在 com/itlaobing/integration/beans.xml 文件中添加以下内容:
<!-- 扫描所有的添加了 @Repository 注解的 Mapper 接口 -->
<context:component-scan base-package="com.itlaobing.integration.mapper" />
4.5、测试
public class MapperTest {
public static void main(String[] args) {
String configLocations = "classpath:com/itlaobing/integration/beans.xml" ;
ApplicationContext container = new ClassPathXmlApplicationContext( configLocations ) ;
CustomerMapper mapper = container.getBean( "customerMapper" , CustomerMapper.class );
System.out.println( mapper );
System.out.println( mapper.getClass() );
System.out.println( Arrays.toString( mapper.getClass().getInterfaces() ));
Customer c = mapper.find( 1 );
System.out.println( c.getUsername() + " : " + c.getName() );
}
}
5、事务
注意,事务控制代码应该在 业务层 织入 而不是 数据访问层。
5.0、提供业务层
package com.itlaobing.integration.service;
import com.itlaobing.integration.entity.Customer;
import com.itlaobing.integration.mapper.CustomerMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class CustomerService {
@Autowired
private CustomerMapper customerMapper;
public Customer loadCustomer( Integer id ) {
return customerMapper.find( id );
}
public boolean updateCustomer( Customer c ){
int count = customerMapper.update( c );
return count == 1 ;
}
public CustomerMapper getCustomerMapper() {
return customerMapper;
}
public void setCustomerMapper(CustomerMapper customerMapper) {
this.customerMapper = customerMapper;
}
}
5.1、提供平台事务管理器
在 com/itlaobing/integration/beans.xml 文件中添加以下内容:
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager" >
<property name="dataSource" ref="dataSource" />
</bean>
5.2、提供 声明式事务 支持
在 com/itlaobing/integration/beans.xml 文件中添加以下内容:
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="update*" isolation="READ_COMMITTED" propagation="REQUIRED" read-only="false" />
<tx:method name="delete*" isolation="READ_COMMITTED" propagation="REQUIRED" read-only="false" />
<tx:method name="save*" isolation="READ_COMMITTED" />
<tx:method name="load*" isolation="READ_COMMITTED" read-only="true" />
<tx:method name="find*" isolation="READ_COMMITTED" read-only="true"/>
<tx:method name="get*" isolation="READ_COMMITTED" read-only="true"/>
</tx:attributes>
</tx:advice>
5.3、提供 事务切面
在 com/itlaobing/integration/beans.xml 文件中添加以下内容:
<aop:config proxy-target-class="true">
<aop:pointcut id="txPointcut" expression="execution(* com..service.*.*(..))" />
<aop:advisor pointcut-ref="txPointcut" advice-ref="txAdvice" />
</aop:config>