Spring笔记三 - 持久层整合
作者: Wyt
系列文章目录
Spring笔记(1) - 工厂
Spring笔记(2) - AOP编程
Spring笔记(3) - 持久层整合
Spring笔记(4) - MVC框架整合
Spring笔记(5) - 注解编程入门
Spring笔记(6) - 注解编程基础
文章目录
1. 背景知识
1.1 持久层技术对Spring的重要意义
1. JavaEE开发需要持久层进行数据库的访问操作
2. 使用 JDBC Hibernate MyBatis 等常见的持久层访问方式进行持久开发过程存在大量的代码冗余
3. Spring 基于模板设计模式对于上述的持久层技术进行了封装
1.2 Spring可以与那些持久层技术进行整合
1. JDBC
|- JDBCTemplate
2. Hibernate
|- HibernateTemplate
3. MyBatis
|- SqlSessionFactoryBean MapperScannerConfigure
2. Spring与MyBatis的整合
* 针对MyBatis在开发的过程中令人不满意的地方, Spring重新对其进行合理的封装, 使开发过程中所书写的代码更简洁
2.1 MyBatis开发步骤的回顾
配置繁琐, 代码冗余
1. 实体
2. 实体别名 配置繁琐
3. 表
4. 创建DAO接口
5. 实现Mapper文件
6. 注册Mapper文件 配置繁琐
7. MybatisAPI的调用 代码冗余
2.2 MyBatis的详细开发步骤
2.2.1 pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<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.wyt</groupId>
<artifactId>spring_mybatis</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8 </maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.48</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.1.14.RELEASE</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.2</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.1.14.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.1.14.RELEASE</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.18</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.8</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.7</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
</dependencies>
</project>
2.2.2 src.main.java.com.wyt.mybatis
- User.java
package com.wyt.mybatis;
import java.io.Serializable;
public class User implements Serializable {
private Integer id;
private String username;
private String password;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
}
- UserDao.java (Interface)
package com.wyt.mybatis;
public interface UserDao {
public void save(User user);
}
2.2.3 src.main.resource
- mybatis-config.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>
<typeAlias alias="user" type="com.wyt.mybatis.User"/>
</typeAliases>
<environments default="mysql">
<environment id="mysql">
<transactionManager type="JDBC"></transactionManager>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/XXX?useSSL=false"/>
<property name="username" value="XXX"/>
<property name="password" value="XXX"/>
</dataSource>
</environment>
</environments>
</configuration>
- UserDAOMapper.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="com.wyt.mybatis.UserDao">
<insert id="save" parameterType="user">
<!--书写SQL语句-->
insert into students.user (username, password) values (#{username}, #{password})
</insert>
</mapper>
2.2.4 在mybatis-config.xml中进行注册
<mappers>
<mapper resource="UserDAOMapper.xml"/>
</mappers>
2.2.5 测试代码
- 在test.java文件夹中创建TestMybatis.java作为测试类
@Test
public void Test01() throws IOException {
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
//获取SqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
//获取SqlSession对象
SqlSession session = sqlSessionFactory.openSession();
//获取UserDao对象
UserDao userDao = session.getMapper(UserDao.class);
User user = new User();
user.setUsername("wyt");
user.setPassword("123456");
userDao.save(user);
//提交session(事务)
session.commit();
}
2.3 MyBatis开发中的劣势
1. 实体别名: 每一次引入类, 都要在相应的xml文档中进行别名的配置, 配置繁琐
如mybatis-conofig.xml配置文件中的代码所示
<!--别名-->
<typeAliases>
<typeAlias alias="user" type="com.wyt.mybatis.User"/>
</typeAliases>
2. 注册Mapper文件: 每有一个Mapper的配置文件都需要引入, 配置繁琐
如mybatis-conofig.xml配置文件中的代码所示
<mappers>
<mapper resource="UserDAOMapper.xml"/>
</mappers>
3. MybatisAPI的调用: 为了获取接口的对象, 每一次调用前都要有同样的操作 代码冗余
如下图
2.4 Spring与MyBatis整合的思路分析
1. 创建SqlSessionFactory对象 - SqlSessionFactoryBean
dataSource 连接池(用户自建)
typeAliasesPackage 实体所在的包
由Spring自动创建别名, 别名为类名
mapperLocations 配置文件所在的包以及通配设置
2. 创建DAO接口的实现类
sqlSessionFactoryBean 引入上述SqlSessionFactory对象
basePackage dao接口存放的包
3. 通过getBean()获得dao对象
*注意: 接口的名称第一个字母用小写
2.5 Spring与MyBatis整合开发的基本步骤
2.5.1 对配置文件(applicationContext.xml)进行相关配置
<!--配置: 只是第一次配置略嫌麻烦, 但是后续的开发使用非常便捷-->
<bean id="dataSource" class=""/>
<!--1. 创建SqlSessionFactory-->
<bean id="ssfb" class="SqlSessionFactoryBean">
<property name="dataSource" ref=""/>
<property name="typeAliasesPackage">
<!-- 指定实体类所在的包
如 com.wyt.entity
User Product
-->
</property>
<property name="mapperLocations">
<!--
指定 配置文件(映射文件)的路径 以及通用配置
com.wyt.mapper/*Mapper.xml
配置文件只要放置在该目录下, 并以Mapper.xml结尾, 就能被Spring所感知并将其注册给SqlFactoryBean
-->
</property>
</bean>
<!--
2. 创建DAO接口的实现类
session -> session.getMapper() -> xxxDAO实现类对象
-->
<bean id="scanner" class="MapperScannerConfigure">
<!--Session来自于SqlSessionFactory-->
<property name="sqlSessionFactoryBean" value="ssfb"/>
<property name="basePackage">
<!--指定 DAO接口放置的包 com.wyt.dao-->
</property>
</bean>
2.5.2 编码
* 实战中经常根据需求 所要写的代码
1. 实体
2. 表
3. 创建DAO接口
4. 实现Mapper文件
2.6 Spring与MyBatis整合编码的具体操作
2.6* 备注:
- 因为使用的是jdk15, 较旧版本的Spring框架不支持, 故以下引入最新的依赖jar包进行操作, 编码思路与上述一致
2.6.1 搭建开发环境(jar) - pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<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.wyt</groupId>
<artifactId>spring_mybatis</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>15</maven.compiler.source>
<maven.compiler.target>15</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.48</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.1</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.3.1</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.3.1</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.1</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.8</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.7</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.5</version>
</dependency>
</dependencies>
</project>
2.6.2 java代码的书写
- com.wyt.dao包(存放dao):
- UserDAO.java
package com.wyt.dao;
import com.wyt.entity.User;
public interface UserDAO {
public void save(User user);
}
- com.wyt.entity包(存放实体类):
- User.java
package com.wyt.entity;
import java.io.Serializable;
public class User implements Serializable {
private Integer id;
private String username;
private String password;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
}
2.6.3 Spring配置文件的配置 - applicationContext.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:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd">
<!--1. 连接池-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/students?useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value="wytmysql"/>
</bean>
<!--2. 创建SqlSessionFactory SqlSessionFactoryBean-->
<bean id="sqlSessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<!--entity包存放实体类-->
<property name="typeAliasesPackage" value="com.wyt.entity"/>
<property name="mapperLocations">
<list>
<value>classpath:com.wyt.mapper/*Mapper.xml</value>
</list>
</property>
</bean>
<!--3. 创建DAO对象 MapperScannerConfigure-->
<bean id="scanner" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactoryBean"/>
<!--dao存放的包-->
<property name="basePackage" value="com.wyt.dao"/>
</bean>
</beans>
2.6.4 resourse中com.wyt.mapper
- UserDAOMapper.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="com.wyt.dao.UserDAO">
<insert id="save" parameterType="User">
insert into user (username, password) values (#{username}, #{password})
</insert>
</mapper>
2.6.5 测试代码
在test.java包下新建TestMybatis02.java测试类
-
上面MyBatis的开发中java代码存放在com.wyt.mybatis包中
-
避免在同一个测试类中引用来自不同包的同名类造成麻烦
/**
* 用于测试Spring和 MyBatis的整合
*/
@Test
public void Test01() {
ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml");
UserDAO userDAO = (UserDAO) ctx.getBean("userDAO");
User user = new User();
user.setUsername("JackMa");
user.setPassword("996");
userDAO.save(user);
}
2.7 Spring与MyBatis整合的细节
* 优势:
1. 不需要用到session.commit();
- 问题: Spring与MyBatis整合后, 为什么DAO不提交事务, 但是数据能够插入数据库中
日志: DEBUG SpringManagedTransaction:49 - JDBC Connection [com.mysql.jdbc.JDBC4Connection@XXXXX] will not be managed by Spring
说明Spring没有管理连接对象
本质上控制连接对象(Connection) -> 连接池(DataSource)
1. Mybatis提供的连接池对象 -> 创建Connection
Connection.setAutoCommit(false) 手工的控制了事务 , 操作完成后,手工提交
2. Druid(C3P0 DBCP)作为连接池 -> 创建Connection
Connection.setAutoCommit(true) true默认值 保持自动控制事务,一条sql 自动提交
-答案:
因为Spring与Mybatis整合时,引入了外部连接池对象,保持自动的事务提交这个机制(Connection.setAutoCommit(true)),不需要手工进行事务的操作,也能进行事务的提交
注意:未来实战中,还会手工控制事务(多条sql一起成功,一起失败),后续Spring通过事务控制解决这个问题。
3. Spring的事务处理
3.1 什么是事务
* 概念: 保证业务操作完整性的一种数据库机制
事务的四大特点: A C I D
1. A 原子性
2. C 一致性
3. I 隔离性
4. D 持久性
3.2 如何控制事务
JDBC:
Connection.setAutoCommit(false);
Connection.commit();
Connection.rollback();
Mybatis:
Mybatis自动开启事务
sqlSession(Connection).commit();
sqlSession(Connection).rollback();
* 结论:控制事务的底层 都是Connection对象完成的。
3.3 Spring控制事务的开发
Spring是通过AOP的方式进行事务开发
3.3.1 原始对象
public class XXXUserServiceImpl{
private xxxDAO xxxDAO
set get
1. 原始对象 --> 原始方法 --> 核心功能 (业务处理+DAO调用)
2. DAO作为Service的成员变量,依赖注入的方式进行赋值
}
3.3.2 额外功能
1. org.springframework.jdbc.datasource.DataSourceTransactionManager
2. 注入DataSource
1. MethodInterceptor
public Object invoke(MethodInvocation invocation){
try{
Connection.setAutoCommit(false);
Object ret = invocation.proceed();
Connection.commit();
}catch(Exception e){
Connection.rollback();
}
return ret;
}
2. @Aspect
@Around
3.3.3 切入点
@Transactional
事务的额外功能加入给那些业务方法。
1. 类上:类中所有的方法都会加入事务
2. 方法上:这个方法会加入事务
3.3.4 组装切面
1. 切入点
2. 额外功能
<tx:annotation-driven transaction-manager=""/>
3.4 Spring控制事务的编码
- 搭建开发环境 (jar)
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.3.1</version>
</dependency>
-
编码
-
com.wyt.service包
-
UserService.java
package com.wyt.service; import com.wyt.entity.User; public interface UserService { public void register(User user); }
-
UserServiceImpl.java
package com.wyt.service; import com.wyt.dao.UserDAO; import com.wyt.entity.User; import org.springframework.transaction.annotation.Transactional; @Transactional public class UserServiceImpl implements UserService{ private UserDAO userDAO; public UserDAO getUserDAO() { return userDAO; } public void setUserDAO(UserDAO userDAO) { this.userDAO = userDAO; } @Override public void register(User user) { userDAO.save(user); } }
- applicationContext.xml
<bean id="userService" class="com.wyt.service.UserServiceImpl">
<property name="userDAO" ref="userDAO"/>
</bean>
<!--DataSourceTransactionManager-->
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>
- 细节
<tx:annotation-driven transaction-manager="dataSourceTransactionManager" proxy-target-class="true"/>
进行动态代理底层实现的切换 proxy-target-class
默认 false JDK
true Cglib