学习内容
一、Spring JDBC
- 数据库的操作由持久层来实现
- 传统JDBC开发中,会频繁的打开、关闭数据库连接等资源操作,产生大量重复代码,效率低
- Spring JDBC是Spring框架提供的持久层功能,可以管理数据库连接资源,简化传统JDBC操作,提升数据库操作效率
1.JdbcTemplate概述
- Spring框架提供了JdbcTemplate类操作数据库,包含的基本方法有数据的添加、删除、查询和更新
- JdbcTemplate类继承自抽象类JdbcAccessor,实现了JdbcTemplate接口
- JdbcAccessor抽象类中提供访问数据库时的公共属性如下:
- DataSource: 获取设置数据库连接,还提供对数据库连接的缓冲池和分布式事务的支持
- SQLExceptionTranslator:是一个接口,它负责对SQLException异常进行转译工作
2.Spring JDBC的配置
- Spring JDBC开发依赖的jar包:spring-jdbc.jar、spring-tx.jar、mysql-connector-java.jar
- spring-jdbc.jar包结构如下
- spring-jdbc主要由4个包组成,具体如下
包名 | 说明 |
---|---|
core(核心包) | 包含了JDBC的核心功能。****包括JdbcTemplate类、SimpleJdbcInsert类、SimpleJdbcCall类以及NamedParameterJdbcTemplate类。 |
dataSource(数据源包) | 包含****访问数据源的实用工具类。有多种数据源的实现,可以在Java EE容器外部测试JDBC代码。 |
object(对象包) | 以面向对象的方式访问数据库,它可以执行查询、修改和更新操作并将返回结果作为业务对象,并且在数据表的列和业务对象的属性之间映射查询结果。 |
support(支持包) | 包含core和object包的支持类,如提供异常转换功能的SQLException类。 |
- 使用JDBC的步骤
- 第一步,导入相关依赖包spring-jdbc.jar、spring-tx.jar、mysql-connector-java.jar包
- 第二步,在spring的配置文件中进行如下配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
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">
<!--1.配置数据源-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<!--数据库驱动-->
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<!--用户名-->
<property name="username" value="root"/>
<!--密码-->
<property name="password" value="123456"/>
<!--连接数据库url-->
<property name="url" value="jdbc:mysql://localhost:3306/spring?useUnicode=true&characterEncoding=utf-8&useSSL=true"/>
</bean>
<!--2.配置JdbcTemplate-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--3.配置注入类-->
<bean id="xxx" class="Xxx">
<property name="jdbcTemplate" ref="jdbcTemplate"/>
</bean>
</beans>
- dataSource数据源配置说明:使用DriverManagerDataSource类,4个属性含义如下。
属性名 | 含义 |
---|---|
driverClassName | 所使用的驱动名称,对应驱动JAR包中的Driver类 |
url | 数据源地址 |
username | 访问数据库的用户名 |
password | 访问数据库的密码 |
二、JdbcTemplate的常用方法
实验环境准备
第一步:在ssm_spring项目下建spring-chap09-jdbc模块,目录结构如下:
第二步:为项目的模块添加依赖,依赖06单元配置好的lib库,还要加上jdbc的依赖包相关依赖包spring-jdbc.jar、spring-tx.jar、mysql-connector-java.jar(必须包含下图中的jar包)
1.execute()方法
- execute()方法用业执行sql语句
- 语法格式如下
- jdbcTemplate.execute("sql语句")
【示例】spring框架下,使用JdbcTemplate类在数据spring中创建account数据表。创表SQL语句如下:
create table account(
id int primary key auto_increment,
username varchar(50),
balance double
);
实现步骤如下:
(1)创建数据库spring
(2)确保按实验环境导入必要的依赖包
(3)编写spring配置文件spring-config.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--1.配置数据源-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<!--数据库驱动-->
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<!--用户名-->
<property name="username" value="root"/>
<!--密码-->
<property name="password" value="123456"/>
<!--连接数据库url-->
<property name="url" value="jdbc:mysql://localhost:3306/spring?useUnicode=true&characterEncoding=utf-8&useSSL=true"/>
</bean>
<!--2.配置JdbcTemplate-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
</beans>
(4)编写execute()方法使用测试类
- 为了方便测试,请导入单元测试包junit.jar
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;
public class TestJDBC {
@Test
public void testExecute() {
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
JdbcTemplate jdbc = context.getBean(JdbcTemplate.class);
String sql = "create table account(" +
"id int primary key auto_increment," +
"username varchar(50)," +
"balance double" +
")";
jdbc.execute(sql);
System.out.println("account数据表创建成功!");
}
}
查看结果:在数据库spring中有了account数据表。
2.update()方法
- update()方法用来执行插入、删除、修改数据表的SQL语句
- 常用update()方法如下
****方法 | ****说明 |
---|---|
****int update(String sql) | 该方法是最简单的update()方法重载形式,它直接执行传入的SQL语句,并返回受影响的行数。 |
****int update(String sql,Object… args) | 该方法可以为SQL语句设置多个参数,这些参数保存在参数args中,使用Object…设置SQL语句中的参数,要求参数不能为NULL,并返回受影响的行数。 |
int update(PreparedStatementCreator psc) | 该方法执行参数psc返回的语句,然后返回受影响的行数。 |
int update(String sql, PreparedStatementSetter pss) | 该方法通过参数pss设置SQL语句中的参数,并返回受影响的行数。 |
【示例】完成对上述示例中account数据表的插入、删除、修改操作。
第一步:确认spring数据库中存在数据表account
第二步:确认Spring JDBC开发的依赖包均已导入
第三步:编写account数据表的pojo类**(放com.jdbc.pojo包)**
package com.jdbc.pojo;
public class Account {
private int id;
private String username;
private double balance;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
@Override
public String toString() {
return "Account{" +
"id=" + id +
", username='" + username + '\'' +
", balance='" + balance + '\'' +
'}';
}
}
第四步,编写数据持久层 -- dao层**(放com.jdbc.dao包)**
- 编写dao层接口
package com.jdbc.dao;
import com.jdbc.pojo.Account;
public interface AccountDao {
//添加记录
int addAccount(Account account);
//按id修改记录
int updateAccountById(Account account);
//按id修改存款金额
int updateBalanceById(int id,double balance);
//按id删除记录
int deleteAccountById(int id);
}
- 编写类,实现dao层接口
package com.jdbc.dao;
import com.jdbc.pojo.Account;
import org.springframework.jdbc.core.JdbcTemplate;
public class AccountDaoImpl implements AccountDao {
//定义JdbcTemplate对象
private JdbcTemplate jdbcTemplate;
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
//添加记录
@Override
public int addAccount(Account account) {
String sql = "insert account (username,balance) values(?,?)";
//设置参数对象
Object args[]=new Object[]{ account.getUsername(), account.getBalance() };
int row = jdbcTemplate.update(sql, args);
return row;
}
//按id修改记录
@Override
public int updateAccountById(Account account) {
String sql = "update account set username=?,balance=? where id=?";
//设置参数对象
Object args[]=new Object[]{ account.getUsername(), account.getBalance(), account.getId() };
int row = jdbcTemplate.update(sql, args);
return row;
}
//按id修改存款金额
@Override
public int updateBalanceById(int id, double balance) {
String sql = "update account set balance=? where id=?";
Object args[]=new Object[]{balance,id};
int row = jdbcTemplate.update(sql, args);
return row;
}
//删除记录
@Override
public int deleteAccountById(int id) {
String sql = "delete from account where id=?";
int row = jdbcTemplate.update(sql, id);
return row;
}
}
第五步,设置spring配置文件spring-config.xml**(放resources文件夹)**
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
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">
<!--1.配置数据源-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<!--数据库驱动-->
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<!--用户名-->
<property name="username" value="root"/>
<!--密码-->
<property name="password" value="123456"/>
<!--连接数据库url-->
<property name="url" value="jdbc:mysql://localhost:3306/spring?useUnicode=true&characterEncoding=utf-8&useSSL=true"/>
</bean>
<!--2.配置JdbcTemplate-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--3.配置注入类-->
<bean id="accountDao" class="com.jdbc.dao.AccountDaoImpl">
<property name="jdbcTemplate" ref="jdbcTemplate"/>
</bean>
</beans>
第六步,编写测试类
import com.jdbc.dao.AccountDao;
import com.jdbc.pojo.Account;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;
public class TestJDBC {
@Test
public void testUpdate_add() {
//初始化spring容器
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
//从spring容器中获取AccountDao实例
AccountDao dao = context.getBean(AccountDao.class);
//设置要添加的数据
Account account=new Account();
account.setUsername("Tom");
account.setBalance(1000.00);
//调用dao的方法添加记录
int row = dao.addAccount(account);
if(row>0){
System.out.println(row+"条记录插入成功!");
}else {
System.out.println("插入记录失败!");
}
}
@Test
public void testUpdate_update() {
//初始化spring容器
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
//从spring容器中获取AccountDao实例
AccountDao dao = context.getBean(AccountDao.class);
//设置要修改的数据
Account account=new Account();
account.setId(1);
account.setUsername("Tom");
account.setBalance(1500.00);
//调用dao的updateAccountById方法修改记录
int row = dao.updateAccountById(account);
if(row>0){
System.out.println(row+"条记录修改成功!");
}else {
System.out.println("修改记录失败!");
}
System.out.println("==============================");
//调用dao的updateBalanceById方法修改记录
row = dao.updateBalanceById(2,2680.00);
if(row>0){
System.out.println(row+"条记录修改成功!");
}else {
System.out.println("修改记录失败!");
}
}
@Test
public void testUpdate_delete() {
//初始化spring容器
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
//从spring容器中获取AccountDao实例
AccountDao dao = context.getBean(AccountDao.class);
//调用dao的方法删除记录
int row = dao.deleteAccountById(2);
if(row>0){
System.out.println(row+"条记录删除成功!");
}else {
System.out.println("删除记录失败!");
}
}
}
3.query()方法
- query()方法用来执行查询数据表的SQL语句
- 常用query()方法如下
****方法 | ****说明 |
---|---|
****List query(String sql, RowMapper rowMapper) | 执行String类型参数提供的SQL语句,并****通过参数rowMapper返回一个List类型的结果。 |
****List query(String sql,Object[] args,RowMapper rowMapper) | 使用****Object[]的值来设置SQL语句中的参数值,rowMapper是个回调方法,直接返回List类型的数据。 |
****queryForObject(String sql,RowMapper rowMapper, Object… args) | 将args参数绑定到SQL语句中,并通过****参数rowMapper返回一个Object类型的单行记录。 |
queryForObject(String sql,RowMapper rowMapper, Object… args) List query(String sql, PreparedStatementSetter pss, RowMapper rowMapper) | 根据String类型参数提供的SQL语句创建PreparedStatement对象,通过参数rowMapper将结果返回到List中。 |
queryForList(String sql,Object[] args, class elementType) | 该方法可以返回多行数据的结果,但必须返回列表,args参数是sql语句中的参数,elementType参数返回的是List数据类型。 |
【示例】完成对上述示例中account数据表的查询操作。
第一步:确认spring数据库中存在数据表account。如果没有,可以运行如下脚本:
USE spring;
DROP TABLE IF EXISTS account;
# 创建数据表
CREATE TABLE account (
id int(11) NOT NULL AUTO_INCREMENT,
username varchar(50) DEFAULT NULL,
balance double DEFAULT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
# 插入数据
insert into account(id,username,balance) values
(1,'zhangsan',100),(2,'lisi',500),(3,'wangwu',300);
第二步:确认Spring JDBC开发的依赖包均已导入
第三步:编写account数据表的pojo类**(同前)**
第四步,编写数据持久层 -- dao层**(放com.jdbc.pojo包)**
- 编写dao层接口
package com.jdbc.dao;
import com.jdbc.pojo.Account;
import java.util.List;
public interface AccountDao {
//按id查询数据表account
Account findAccountById(int id);
//查询数据表account所有记录
List<Account> findAllAccount();
}
- 编写类,实现dao层接口
package com.jdbc.dao;
import com.jdbc.pojo.Account;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import java.util.List;
public class AccountDaoImpl implements AccountDao {
//定义JdbcTemplate对象
private JdbcTemplate jdbcTemplate;
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
//按id查询数据表account
@Override
public Account findAccountById(int id) {
String sql = "select * from account where id=?";
RowMapper<Account> rowMapper = new BeanPropertyRowMapper<>(Account.class);
Account account = jdbcTemplate.queryForObject(sql, rowMapper, id);
return account;
}
//查询数据表account所有记录
@Override
public List<Account> findAllAccount() {
String sql = "select * from account";
RowMapper<Account> rowMapper = new BeanPropertyRowMapper<>(Account.class);
List<Account> accounts = jdbcTemplate.query(sql, rowMapper);
return accounts;
}
}
第五步,设置spring配置文件spring-config.xml**(同前)**
第六步,编写测试类
import com.jdbc.dao.AccountDao;
import com.jdbc.pojo.Account;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;
import java.util.List;
public class TestJDBC {
@Test
public void testQuery() {
//初始化spring容器
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
//从spring容器中获取AccountDao实例
AccountDao dao = context.getBean(AccountDao.class);
//调用dao的方法查询id号为3的记录并显示
Account account = dao.findAccountById(3);
System.out.println(account);
System.out.println("======================");
//调用dao的方法查询所有记录并显示
List<Account> accounts = dao.findAllAccount();
for (Account acc : accounts) {
System.out.println(acc);
}
}
}
三、Spring事务管理概述
- 实际开发中,数据库操作会涉及到事务管理。如:某商品入库时,明细表和库存表都要更新数据,为确保两表同时更新,需要事务管理
- Spring提供了事务管理API,简化事务管理流程
1.事务管理核心接口
- 事务管理依赖包:spring-tx.jar
- 该包提供3个接口实现事务管理
- PlatformTransactionManager接口:根据属性管理事务。
- TransactionDefinition接口:定义事务的属性。
- TransactionStatus接口:界定事务的状态 。
(1)PlatformTransactionManager接口
主要方法如下。
****方法 | ****说明 |
---|---|
TransactionStatus | 获取事务状态信息 |
void commit(TransactionStatus status) | 提交事务 |
void rollback(TransactionStatus status) | 回滚事务 |
注意:
- 实际应用中,Spring事务管理由具体的持久化技术完成,PlatformTransactionManager接口只提供统一的抽象方法。
- 对于不同持久化技术,Spring提供了不同的实现类。如:为Spring JDBC和Mybatis等依赖DataSource的持久化技术提供了DataSourceTransactionManager实现类。
(2)TransactionDefinition接口
- TransactionDefinition接口中定义了事务描述相关的常量,事务描述相关常量包括:事务的隔离级别、传播行为、超时时间、是否为只读事务
- 事务隔离级别: 有以下5几种。
****隔离级别 | ****说明 |
---|---|
ISOLATION_DEFAULT | 采用当前数据库默认的事务隔离级别。 |
ISOLATION_READ_UNCOMMITTED | 读未提交。允许另外一个事务读取到当前未提交的数据,隔离级别最低,****可能会导致脏读、幻读或不可重复读。 <br>脏读:********事务A在处理过程中读取了事务B未提交的数据。<br>****幻读: 事务A读取与搜索条件相匹配的若干行。之后,事务B以插入或删除行等方式来修改事务A的结果集,然后再提交。****可能造成事物A第一次与二次读的数据不同。<br>不可重复读:********一个事务范围内,****多次查询某个数据,由于数据没锁住,可能被别的事务更新导致可能得到不同的结果。 |
ISOLATION_READ_COMMITTED | 读已提交。被一个事务修改的数据提交后才能被另一个事务读取,****可以避免脏读,无法避免幻读,而且不可重复读。 |
ISOLATION_ REPEATABLE_READ | 允许重复读,可以避免脏读,资源消耗上升。这是MySQL数据库的默认隔离级别。 |
REPEATABLE_SERIALIZABLE | 事务串行执行,也就是按照时间顺序一一执行多个事务,不存在并发问题,最可靠,但性能与效率最低。 |
- **事务传播行为:** 指处于不同事务中的方法在相互调用时,方法执行期间,事务的维护情况。有以下7种,开发人员要根据实际情况进行选择。
****传播行为 | ****说明 |
---|---|
PROPAGATION_REQUIRED | 如果当前存在一个事务,则加入该事务;如果当前没有事务,则创建一个新的事务。 ****默认的事务传播行为!! |
PROPAGATION_SUPPORTS | 支持当前事务。允许如果当前存在一个事务,则加入该事务;如果当前没有事务,则以非事务方式执行。 |
PROPAGATION_MANDATORY | 当前必须存在一个事务,如果没有,就抛出异常。 |
PROPAGATION_REQUIRES_NEW | 创建一个新的事务。如果当前已存在一个事务,将已存在的事务挂起。 |
PROPAGATION_NOT_SUPPORTED | 不支持事务。在没有事务的情况下执行,如果当前已存在一个事务,则将已存在的事务挂起。 |
ROPAGATION_NEVER | 永远不支持当前事务。如果当前已存在一个事务,则抛出异常。 |
PROPAGATION_NESTED | 如果当前存在事务,则在当前事务的一个子事务中执行。 |
- **事务超时时间:** 指事务执行的时间界限,超过这个时间界限,事务将会回滚
- TransactionDefinition接口提供了**TIMEOUT_DEFAULT常量**定义事务的超时时间。
- **事务是否只读**
- 当事务为只读时,该事务不修改任何数据。如果在只读事务中修改数据,会引发异常
- 只读事务有助于提升性能
- TransactionDefinition接口中获取事务属性的方法
****方法 | ****说明 |
---|---|
int getPropagationBehavior() | 返回事务的传播行为 |
int getIsolationLevel() | 返回事务的隔离层次 |
int getTimeout() | 返回事务的超时属性 |
boolean isReadOnly() | 判断事务是否为只读 |
String getName() | 返回定义的事务名称 |
(3)TransactionStatus接口
- 该接口用于界定事务状态
- 提供返回事务状态信息的方法
****方法 | ****说明 |
---|---|
boolean isNewTransaction() | 判断当前事务是否为新事务 |
boolean hasSavepoint() | 判断当前事务是否创建了一个保存点 |
boolean isRollbackOnly() | 判断当前事务是否被标记为rollback-only |
void setRollbackOnly() | 将当前事务标记为rollback-only |
boolean isCompleted() | 判断当前事务是否已经完成(提交或回滚) |
void flush() | 刷新底层的修改到数据库 |
2.事务管理的方式
有两种。
- 编程式事务管理: 通过编写代码实现的事务管理。包括定义事务的开始、正常执行后的事务提交和异常时的事务回滚。
- 声明式事务管理:通过AOP技术实现的事务管理,其主要思想是将事务管理作为一个“切面”代码单独编写,然后通过AOP技术将事务管理的“切面”代码植入到业务目标类中。
四、声明式事务管理
1.基于XML方式的声明式事务
- 在spring配置文件中,配置事务规则的相关声明来实现事务管理
- XML文件配置声明式事务一般步骤
- 第一步:导入依赖包spring-tx.jar、spring-aop.jar、aopalliance.jar、aspectjweaver.jar**(后三个提供AOP)**
- 第二步:引入tx、aop约束(命名空间)
- 第三步:配置事务管理器类DataSourceTransactionManager的bean,要注入DataSource属性
- 第四步:用 tx:advice及其子元素 tx:attributes、 tx:method配置事务管理的通知。
- tx:advice元素的transaction-manager属性指向第三步的bean
- tx:method元素设置事务方法名、隔离级别、传播行为等。
- 第五步:用aop:config及其子元素aop:pointcut、aop:advisor配置AOP,将通知切入到需要事务的方法中去
- tx:advice元素属性说明如下:
- id属性:配置文件中唯一标识
- transaction-manager属性:指定事务管理器
- tx:advice元素的子元素 tx:attributes下可以配置多个 tx:method子元素
- tx:method子元素常用属性如下
****属性 | ****说明 |
---|---|
name | 用于指定方法名的匹配模式,该属性为必选属性,它指定了与事务属性相关的方法名。 |
propagation | 用于指定事务的传播行为。 |
isolation | 用于指定事务的隔离级别。 |
read-only | 用于指定事务是否只读。 |
timeout | 用于指定事务超时时间。 |
rollback-for | 用于指定触发事务回滚的异常类。 |
no-rollback-for | 用于指定不触发事务回滚的异常类。 |
【示例】在前述有关account数据表操作示例的基础上,编写一个模拟银行转账的程序 ,要求转账时通过Spring对事务进行控制。
(1)导入依赖包
- 事务管理包:spring-tx.jar
- AOP相关包:spring-aop.jar、aopalliance.jar、aspectjweaver.jar
- Spring JDBC数据库编程包:spring-jdbc.jar、mysql-connector-java-5.1.47.jar
- Spring中bean管理相关包:spring-beans-5.3.12.jar、spring-context-5.3.12.jar、spring-core-5.3.12.jar、spring-expression-5.3.12.jar、commons-logging-1.2.jar
- 如果想单元测试,还要导入junit.jar包
(2)编写dao层**(同前述有关account数据表的添加、删除、修改、查询操作示例)**
(3)编写service层
- 编写AccountService接口**(放com.jdbc.service包)**
package com.jdbc.service;
public interface AccountService {
/**
* 转账
* @param sourceId 转出账户的id
* @param targetId 转入账户的id
* @param money 转账金额
*/
void transfer(int sourceId,int targetId,double money);
}
- 编写类,实现AccountService接口**(放com.jdbc.service包)**
package com.jdbc.service;
import com.jdbc.dao.AccountDao;
import com.jdbc.pojo.Account;
public class AccountServiceImpl implements AccountService{
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
/**
* 转账
* @param sourceId 转出账户的id
* @param targetId 转入账户的id
* @param money 转账金额
*/
@Override
public void transfer(int sourceId, int targetId, double money) {
//分别得到sourceId\targetId账户原金额
double sourceMoney = accountDao.findAccountById(sourceId).getBalance();
double targetMoney = accountDao.findAccountById(targetId).getBalance();
//扣减sourceId账户金额数量
accountDao.updateBalanceById(sourceId,sourceMoney-money);
int x=1/0; //此处人为设置异常!!以便体会事务管理的重要性!
//增加targetId账户金额数量
accountDao.updateBalanceById(targetId,targetMoney+money);
}
}
(4)设置spring配置文件spring-config.xml**(放resources文件夹)**
<?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: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/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!--1.配置数据源-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<!--数据库驱动-->
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<!--用户名-->
<property name="username" value="root"/>
<!--密码-->
<property name="password" value="123456"/>
<!--连接数据库url-->
<property name="url" value="jdbc:mysql://localhost:3306/spring?useUnicode=true&characterEncoding=utf-8&useSSL=true"/>
</bean>
<!--2.配置JdbcTemplate-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--3.配置注入类-->
<!--使用属性注入-->
<bean id="accountDao" class="com.jdbc.dao.AccountDaoImpl">
<property name="jdbcTemplate" ref="jdbcTemplate"/>
</bean>
<!--使用了自动装配-->
<bean id="accountService" class="com.jdbc.service.AccountServiceImpl" autowire="byType"/>
</beans>
(5)编写测试类
import com.jdbc.dao.AccountDao;
import com.jdbc.pojo.Account;
import com.jdbc.service.AccountService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;
import java.util.List;
public class TestJDBC {
@Test
public void testTransfer(){
//初始化spring容器
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
//从spring容器中获取AccountService实例
AccountService service = context.getBean(AccountService.class);
//调用转账方法
service.transfer(2,3,200);
System.out.println("转账成功...");
}
}
注意:
- 运行该测试类时,由于第4步没有配置事务管理,查看account数据表发现:转出账户存款减少,但转入账户存款并没有增加,数据处理有误。系统无法保证数据的安全性与一致性!!
(6)为了让操作数据库时,数据处理正确,应该在spring-config.xml加上如下事务管理配置
<!--4.配置事务-->
<!--(1)配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager" autowire="byType"/>
<!--(2)编写通知:对事务进行增强,需要编写切入点和具体执行事务的细节-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!-- name属性值为*,表示所有方法-->
<tx:method name="*" propagation="REQUIRED" isolation="DEFAULT" read-only="false"/>
</tx:attributes>
</tx:advice>
<!--(3)编写AOP:让Spring自动为目标生成代理,需要使用AspectJ的表达式-->
<aop:config>
<!-- 切入点 -->
<aop:pointcut id="txPointCut" expression="execution(* com.jdbc.service.*.*(..))"/>
<!-- 切面:将切入点和通知整合 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
</aop:config>
再次运行测试类,转入转出账户数据会同步处理时,同于切入了事务,保证了数据的安全性与一致性!!
2.基于注解方式的声明式事务
- @Transactional注解:写在方法上方为该方法提供事务管理。有一系列属性配置事务,含义如下:
****属性 | ****说明 |
---|---|
value | 用于指定使用的事务管理器 |
propagation | 用于指定事务的传播行为 |
isolation | 用于指定事务的隔离级别 |
timeout | 用于指定事务的超时时间 |
readonly | 用于指定事务是否为只读 |
rollbackFor | 用于指定导致事务回滚的异常类数组 |
rollbackForClassName | 用于指定导致事务回滚的异常类名称数组 |
noRollbackFor | 用于指定不会导致事务回滚的异常类数组 |
noRollbackForClassName | 用于指定不会导致事务回滚的异常类名称数组 |
- 基于注解方式的声明式事务一般步骤
- 第一步:导入依赖包spring-tx.jar、spring-aop.jar**(后一个提供AOP)**
- 第二步:引入tx约束**(命名空间)**
- 第三步:配置事务管理器类DataSourceTransactionManager的bean,要注入DataSource属性
- 第四步:注册事务管理器驱动
<!--注册事务管理器-->
<tx:annotation-driven transaction-manager="事务管理器bean的id"/>
- 第五步:在需要事务管理的方法上方加注解@Transactional
【示例】使用注解完成前述模拟银行转账示例。
仅修改以下两个地方,其它不变!
(1)为AccountServiceImpl类的transfer方法上方加@Transactional注解
@Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.DEFAULT,readOnly = false)
public void transfer(int sourceId, int targetId, double money) {
//分别得到sourceId\targetId账户原金额
double sourceMoney = accountDao.findAccountById(sourceId).getBalance();
double targetMoney = accountDao.findAccountById(targetId).getBalance();
//扣减sourceId账户金额数量
accountDao.updateBalanceById(sourceId,sourceMoney-money);
int x=1/0; //此处人为设置异常
//增加targetId账户金额数量
accountDao.updateBalanceById(targetId,targetMoney+money);
}
(2)修改配置文件spring-config.xml,修改事务管理配置方法
<!--4.配置事务-->
<!--(1)配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager" autowire="byType"/>
<!--(2)注册事务管理器-->
<tx:annotation-driven transaction-manager="transactionManager"/>
再一次运行测试类,查看数据结果,转入转出账户存款金额是否具有一致性!
五、课后练习
完成下述任务。
1.用注解方式完成对数据表account的插入、删除、修改、查询操作。
2.要求学生在控制台输入用户名密码,如果用户账号密码正确则显示用户所属班级,如果登录失败则显示登录失败。实现用户登录项目运行成功后控制台效果如下所示。
- 数据库为spring
- student数据表结构如下表所示:
字段名 | 类型 | 长度 | 是否主键 | 说明 |
---|---|---|---|---|
id | int | 11 | 是 | 学生编号 |
username | varchar | 255 | 否 | 学生姓名 |
password | varchar | 255 | 否 | 学生密码 |
course | varchar | 255 | 否 | 学生班级 |
- student数据表创建及数据添中脚本如下
use spring;
DROP TABLE IF EXISTS student;
#创建数据表
CREATE TABLE student (
id int(11) NOT NULL AUTO_INCREMENT,
username varchar(255) DEFAULT NULL,
password varchar(255) DEFAULT NULL,
course varchar(255) DEFAULT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
#插入数据
INSERT INTO student VALUES ('1', 'zhangsan', '123456', 'Java');
INSERT INTO student VALUES ('2', 'lisi', '123456', 'HTML');
INSERT INTO student VALUES ('3', 'wangfang', '123456', 'c');
- 思路分析:根据学生管理系统及其登录要求,案例的实现步骤如下:
- 为了存储学生信息,需要创建一个数据库。
- 为了程序连接数据库并完成对数据的增删改查操作,需要在XML配置文件中配置数据库连接和事务等信息。、
- 在Dao层实现查询用户信息的方法。
- 在Controller层处理业务逻辑,如判断用户输入的用户名与密码是否正确 。
- 要求分别用基于注解方式和基于XML方式完成该任务