1. 数据库操作
将关系型数据库中的数据读取到内存中的对象
将内存中的对象数据保存到关系型数据库中
1.1 JDBC技术
1.1.1 常用对象和接口
类/接口 | 说明 |
DriverManager | 用于管理一组JDBC驱动程序的基本服务类。 |
Connection | 数据库连接对象。 |
Statement | 用于执行静态SQL语句并返回其产生的结果的对象。 |
PreparedStatement | 预编译SQL语句,并可设置参数,执行语句并返回其产生的结果的对象。 |
ResultSet | 表示数据库结果集的数据表,通常由执行查询数据库的语句生成。 |
1.1.2 使用步骤
- 使用
Class.forName()
加载驱动 - 通过
DriverManager
获取Connection
(数据库连接)对象 - 定义一个保存
SQL
语句的字符串 - 使用
Connection
对象根据SQL
语句创建一个可执行语句的Statement
/PreparedStatement
对象 - 使用
PreparedStatement
对象设置参数 - 执行
PreparedStatement
的executeUpdate
或executeQuery
方法并得到结果 - 根据业务要求处理结果(查询时需要处理
ResultSet
对象中的数据) - 调用对象的
close
方法关闭连接释放资源
1.1.3 数据转换
数据库中的数据和程序中保存数据的实体类需要实现映射关系
2. Spring的数据访问支持
Spring 提供了专门的数据访问模块,用于集成的各种数据访问框架技术,简化对数据库的操作,同时还提供了统一的事务管理抽象,其底层使用AOP代理实现。
2.1 优点
- 统一处理不同的事务API
- 比如Java Transaction API(JTA)、JDBC、Hibernate和Java Persistence API(JPA)
- 支持声明式事务管理
- 简化事务管理的编码过程
- 优秀的Spring数据访问抽象整合
2.2 核心模块
spring-jdbc:简化了原始JDBC的繁琐操作。
spring-tx:提供了事务管理的支持。
3. spring-jdbc模块
3.1 JdbcTemplate的概述
JdbcTemplate
是JDBC模块中的核心类。它处理资源的创建和释放,这有助于您避免常见的错误,例如忘记关闭连接。它执行核心JDBC工作流程的基本任务(例如,语句创建和执行),而使应用程序代码提供SQL并提取结果,简化了原始的JDBC的繁琐步骤。提供了以下的大致功能:
- 运行SQL查询
- 更新语句和存储过程调用
- 对ResultSet实例执行迭代并提取返回的参数值。
- 捕获JDBC异常并将其转换为org.springframework.dao程序包中定义的通用异常层次结构。
3.2 JdbcTemplate的使用步骤
3.2.1 导入jdbc驱动、druid数据源的坐标
<!--JDBC驱动坐标-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<!--druid数据源坐标-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>${druid.version}</version>
</dependency>
3.2.2 导入spring-context、spring-jdbc模块的坐标
<!--导入spring-context模块的坐标-->
<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>
3.2.3 配置数据源和JdbcTemplate
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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">
<!--加载属性文件-->
<context:property-placeholder location="classpath:database.properties"/>
<!--配置数据源对象-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${druid.driverClassName}"/>
<property name="url" value="${druid.url}"/>
<property name="username" value="${druid.username}"/>
<property name="password" value="${druid.password}"/>
</bean>
<!--配置JdbcTemplate对象-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
</beans>
3.3 使用JdbcTemplate完成CRUD操作
3.3.1 新增操作
package com.xuetang9.spring.dataaccess;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring-jdbc.xml")
public class JdbcTemplateTest {
@Autowired
private JdbcTemplate jdbcTemplate;
@Test
public void testInsert() {
// 创建一个SQL语句
String sql = "insert into section (id, title) values (default, ?)";
// 构建一个参数对象
Object[] params = {"测试版块"};
// 执行
int rows = jdbcTemplate.update(sql,params);
// 断言测试
Assert.assertEquals(1,rows);
}
}
3.3.2 删除操作
package com.xuetang9.spring.dataaccess;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring-jdbc.xml")
public class JdbcTemplateTest {
@Autowired
private JdbcTemplate jdbcTemplate;
@Test
public void testDelete(){
// 创建一个SQL语句
String sql = "delete from section where id = ?";
// 构建一个参数对象
Object[] params = {17};
// 执行
int rows = jdbcTemplate.update(sql,params);
// 断言测试
Assert.assertEquals(1,rows);
}
}
3.3.3 修改操作
package com.xuetang9.spring.dataaccess;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring-jdbc.xml")
public class JdbcTemplateTest {
@Autowired
private JdbcTemplate jdbcTemplate;
@Test
public void testUpdate(){
// 创建一个SQL语句
String sql = "update section set title = ? where id = ?";
// 构建一个参数对象
Object[] params = {"官方", 1};
// 执行
int rows = jdbcTemplate.update(sql,params);
// 断言测试
Assert.assertEquals(1,rows);
}
}
3.3.4 查询操作
package com.xuetang9.spring.dataaccess;
import com.xuetang9.spring.dataaccess.entity.Section;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.List;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring-jdbc.xml")
public class JdbcTemplateTest {
@Autowired
private JdbcTemplate jdbcTemplate;
@Test
public void testSelectList(){
// 创建一个SQL语句
String sql = "select * from section";
// 构建一个参数对象
Object[] params = {};
// 构建一个RowMapper
RowMapper<Section> rowMapper = new BeanPropertyRowMapper(Section.class);
// 执行查询得到结果
List<Section> list = jdbcTemplate.query(sql,rowMapper, params);
System.out.println(list);
}
}
4. spring-tx模块
提供了对事务管理的支持
4.1 事务的相关概念
事务是作为单个逻辑工作单元执行的一系列操作。
多个操作作为一个整体向系统提交,要么都执行,要么都不执行。
事务是一个不可分割的逻辑单元。
4.1.1 事务的特性
特性 | 说明 |
原子性 Atomicity | 事务是一个完整的操作,事务的各不操作都是不可分的(原子的), 要么都执行,要么都不执行。 |
一致性 Consistency | 一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态。 |
隔离性 Isolation | 隔离性是当多个用户并发访问数据库时,不能被其他事务的操作所干扰, 多个并发事务之间要相互隔离。 |
持久性 Durability | 持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的, 即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作 |
4.2 声明式事务
Spring Framework的声明式事务支持,最重要的概念是通过AOP代理启用此支持,并且事务性建议由元数据(当前基于XML或基于注释)驱动。 AOP与事务元数据的组合产生一个AOP代理,该代理使用TransactionInterceptor和PlatformTransactionManager实现来驱动围绕方法调用的事务。
4.2.1 事务控制的相关对象
4.2.1.1 PlatformTransactionManager
该接口是 spring 的事务管理器,它里面提供了我们常用的操作事务的方法。
针对不同的数据访问技术有不同的实现类:
jdbc
或mybatis
:org.springframework.jdbc.datasource.DataSourceTransactionManager
hibernate
:org.springframework.orm.hibernate5.HibernateTransactionManager
JPA
:org.springframework.orm.jpa.JpaTransactionManager
方法 | 说明 |
TransactionStatus getTransaction(TransactionDefinition definition) | 获取事务的状态信息 |
void commit(TransactionStatus status) | 提交事务 |
void rollback(TransactionStatus status) | 回滚事务 |
4.2.1.2 TransactionDefinition
该接口定义事务信息有哪些是事务的定义信息对象,里面有如下方法
方法 | 说明 |
int getPropagationBehavior() | 获取事务传播行为 |
int getIsolationLevel() | 获取事务隔离级别 |
int getTimeout() | 获取事务超时时间,默认-1 |
boolean isReadOnly() | 获取事务是否只读,默认false |
4.2.1.3 TransactionStatus
该接口定义了获取事务状态信息的操作
方法 | 说明 |
boolean hasSavepoint(); | 事务是否在内部携带存储回滚点 |
boolean isNewTransaction(); | 是否是新事务 |
boolean isRollbackOnly() | 事务是否只回滚 |
boolean isCompleted() | 事务是否完成 |
4.2.1.4 事务传播行为(重点背诵理解)
传播类型 | 说明 |
| 表示当前方法必须运行在事务中。如果当前事务存在,方法将会在该事务中运行。否则,会启动一个新的事务 |
| 表示当前方法不需要事务上下文,但是如果存在当前事务的话,那么该方法会在这个事务中运行 |
| 表示该方法必须在事务中运行,如果当前事务不存在,则会抛出一个异常 |
| 表示当前方法必须运行在它自己的事务中。一个新的事务将被启动。如果存在当前事务,在该方法执行期间,当前事务会被挂起。 |
| 表示该方法不应该运行在事务中。如果存在当前事务,在该方法运行期间,当前事务将被挂起 |
| 表示当前方法不应该运行在事务上下文中。如果当前正有一个事务在运行,则会抛出异常 |
| 表示如果当前已经存在一个事务,那么该方法将会在嵌套事务中运行。嵌套的事务可以独立于当前事务进行单独地提交或回滚。如果当前事务不存在,那么其行为与REQUIRED一样。 |
4.2.1.5 事务的隔离级别(重点背诵理解)
隔离级别 | 说明 |
| 使用后端数据库默认的隔离级别 |
| 最低的隔离级别,允许读取尚未提交的数据变更。可能会导致脏读、幻读或不可重复读 |
| 允许读取并发事务已经提交的数据。可以阻止脏读,但幻读或不可重复读仍有可能发生 |
| 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改。可以阻止脏读和不可重复读,但幻读仍有可能发生 |
| 最高的隔离级别,完全服从ACID的隔离级别,确保阻止脏读、不可重复读以及幻读,也是最慢的事务隔离级别,因为它通常是通过完全锁定事务相关的数据库表来实现的 |
4.2.1.6 并发事务出现的问题(重点背诵理解)
现象 | 说明 |
Dirty reads 脏读 | 脏读发生在一个事务读取了另一个事务改写但尚未提交的数据时。如果改写在稍后被回滚了,那么第一个事务获取的数据就是无效的。 |
Nonrepeatable read 不可重复读 | 不可重复读发生在一个事务执行相同的查询两次或两次以上,但是每次都得到不同的数据时。这通常是因为另一个并发事务在两次查询期间进行了更新 |
Phantom read幻读 | 幻读与不可重复读类似。它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录 |
使用乐观锁解决高并发的数据处理
认为每次操作都不会出现 事务问题(脏读、幻读、不可重复读取)
在可能出现高并发的数据库表中添加一列(version)数据,用来标识数据版本
该列数据:可使用时间戳、自增的数据
每次对数据修改都需要修改 版本数据
update 表名 set 列名 = 值, version = version + 1
第一次查询,必须把版本号查询出来
后面修改时 update 表名 set 列名 = 值, version = version + 1 where version = 查询出的版本
4.3 声明式事务控制实现
Spring的声明式事务提供基于XML和注解两种方式实现
事务是一个完整不可分割的操作,通常事务会设置到业务层。
例如:银行存钱、取钱等操作。
4.3.1 基于 XML 的声明式事务控制
4.3.1.1 配置所有需要的依赖坐标
<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>
<spring.version>5.2.7.RELEASE</spring.version>
<mysql.version>8.0.20</mysql.version>
<druid.version>1.1.23</druid.version>
</properties>
<dependencies>
<!--lombok依赖,简化bean的操作-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!--JDBC驱动坐标-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<!--druid数据源坐标-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>${druid.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
<scope>test</scope>
</dependency>
<!--导入spring-context模块的坐标-->
<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>
<!--AOP的依赖坐标-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.6</version>
</dependency>
</dependencies>
4.3.1.2 配置xml中的bean对象组装
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!--加载属性文件-->
<context:property-placeholder location="classpath:database.properties"/>
<!--配置数据源对象-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${druid.driverClassName}"/>
<property name="url" value="${druid.url}"/>
<property name="username" value="${druid.username}"/>
<property name="password" value="${druid.password}"/>
</bean>
<!--配置JdbcTemplate对象-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--配置事务的AOP设置-->
<aop:config>
<!--声明一个切点,表明需要匹配实现事务处理增强业务方法-->
<aop:pointcut id="txPointcut" expression="execution(* com.xuetang9.spring.dataaccess.service..*.*(..))"/>
<!--织入:表名匹配的方法需要使用哪个增强对象-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"></aop:advisor>
</aop:config>
<!--配置的事务增强对象的具体处理,还需要指明事务操作的链接对象从哪里来-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="list*" isolation="DEFAULT" propagation="REQUIRED" read-only="true" timeout="3"/>
<tx:method name="add*" propagation="REQUIRED" rollback-for="java.lang.Exception"/>
<tx:method name="delete*" propagation="REQUIRED" rollback-for="java.lang.Exception"/>
<tx:method name="update*" propagation="REQUIRED" rollback-for="java.lang.Exception"/>
<tx:method name="*" propagation="REQUIRED" rollback-for="java.lang.Exception"/>
</tx:attributes>
</tx:advice>
<!--配置事务管理器,用于提供事务管理的增强操作-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<context:component-scan base-package="com.xuetang9.spring.dataaccess"/>
</beans>
4.3.2 基于配置+注解的声明式事务控制
4.3.2.1 配置所有需要的依赖坐标(参看上面)
4.3.2.2 配置事务的注解扫描驱动
<!--配置事务管理器,用于提供事务管理的增强操作-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--配置事务的注解驱动-->
<tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true"/>
4.3.2.3 在业务层添加事务的注解
package com.xuetang9.spring.dataaccess.service.impl;
import com.xuetang9.spring.dataaccess.dao.SectionDao;
import com.xuetang9.spring.dataaccess.entity.Section;
import com.xuetang9.spring.dataaccess.service.SectionService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import java.util.Arrays;
import java.util.List;
@Service
@Transactional(rollbackFor = {Exception.class}, timeout = 3, readOnly = true)
public class SectionServiceImpl implements SectionService {
@Autowired
private SectionDao sectionDao;
@Transactional(rollbackFor = {Exception.class})
@Override
public boolean addSection(Section section) {
int rows = sectionDao.insert(section);
testException();
return rows > 0;
}
@Override
public List<Section> listAll() {
return null;
}
public void testException() {
Arrays.asList().get(3);
}
}
4.3.3 纯注解形式实现声明式事务控制
4.3.3.1 配置所有需要的依赖坐标(参看上面)
4.3.3.2 创建注解的配置类
package com.xuetang9.spring.dataaccess.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.TransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;
@Configuration
@ComponentScan("com.xuetang9.spring.dataaccess")
@EnableTransactionManagement
@EnableAspectJAutoProxy
public class AppConfig {
@Bean
public JdbcTemplate createJdbcTemplate(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
@Bean
public TransactionManager createTransactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
package com.xuetang9.spring.dataaccess.config;
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import javax.sql.DataSource;
@Configuration
@PropertySource("classpath:database.properties")
public class DatabaseConfig {
@Value("${druid.driverClassName}")
private String driverClassName;
@Value("${druid.url}")
private String url;
@Value("${druid.username}")
private String username;
@Value("${druid.password}")
private String password;
/**
* 把方法的返回值作为bean对象,保存到容器中
* @return
*/
@Bean
public DataSource createDataSource(){
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setDriverClassName(driverClassName);
druidDataSource.setUrl(url);
druidDataSource.setUsername(username);
druidDataSource.setPassword(password);
return druidDataSource;
}
}
4.3.3.3 在业务层添加事务的注解
package com.xuetang9.spring.dataaccess.service.impl;
import com.xuetang9.spring.dataaccess.dao.SectionDao;
import com.xuetang9.spring.dataaccess.entity.Section;
import com.xuetang9.spring.dataaccess.service.SectionService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import java.util.Arrays;
import java.util.List;
@Service
@Transactional(rollbackFor = {Exception.class}, timeout = 3, readOnly = true)
public class SectionServiceImpl implements SectionService {
@Autowired
private SectionDao sectionDao;
@Transactional(rollbackFor = {Exception.class})
@Override
public boolean addSection(Section section) {
int rows = sectionDao.insert(section);
testException();
return rows > 0;
}
@Override
public List<Section> listAll() {
return null;
}
public void testException() {
Arrays.asList().get(3);
}
}