目录
4.2.1 PlatformTransactionManager:平台事务管理器
4.2.2 TransactionDefinition :事务定义信息
4.5 Spring的事务管理:一类:编程式事务(需要手动编写代码)
4.6 Spring的事务管理:二类:声明式事务管理(通过配置实现)---AOP
一.SpringJDBC的入门
- Spring是EE开发的一站式的框架,有EE开发的每层的解决方案。
- Spring对持久层也提供了解决方案:ORM模块和JDBC的模板。
- Spring提供了很多的模板用于简化开发:
1.1 JDBC模板使用入门
①创建项目,新建XML文件引入约束、jar包:数据库驱动,Spring的JDBC模板的jar包
<?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: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/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">
</beans>
②创建数据库和表
create database spring_jdbc;
use spring_jdbc;
create table account(
id int primary key auto_increment,
name varchar(20),
money double
);
③使用jdbc模板保存数据
/**
* JDBC模板的使用
*
*/
public class JdbcDemo1 {
@Test
// jdbc模板的使用类似于Dbutils.
public void demo1(){
// 创建连接池:
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql:///spring_jdbc");
dataSource.setUsername("root");
dataSource.setPassword("root");
// 创建jdbc模板
JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(dataSource);
jdbcTemplate.update("insert into account values (null,?,?)", "赵冠希",10000d);
}
}
1.2 使用Spring配置连接池、JDBC模板
①配置xml文件
<!-- 配置Spring的内置的连接池======================== -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql:///spring_jdbc"/>
<property name="username" value="root"/>
<property name="password" value="abc"/>
</bean>
<!-- 配置Spring的JDBC的模板========================= -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource" />
</bean>
②测试类
引入spring_aop的jar包
二.使用开源的数据库连接池
2.1 DBCP的使用
引入jar包
配置DBCP连接池
<!-- 配置DBCP连接池=============================== -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql:///spring_jdbc"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</bean>
2.2 C3P0的使用
引入c3p0连接池jar包
将数据库信息抽取到文件中
① 在src下建立一个属性文件 jdbc.properties 文件内容:
jdbc.driverClass=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql:///spring_jdbc
jdbc.username=root
jdbc.password=root
② 在applicationContext.xml中引入该属性文件
<!-- 引入属性文件================================== -->
<!-- 第一种方式通过一个bean标签引入的(很少) -->
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location" value="classpath:jdbc.properties"/>
</bean>
<!-- 第二种方式通过context标签引入的 -->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!-- 配置C3P0连接池=============================== -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driverClass}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="user" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
三.jdbc模板的常见操作
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class JdbcDemo2 {
@Resource(name="jdbcTemplate")
private JdbcTemplate jdbcTemplate;
...操作方法...
}
3.1 增删改
// 保存操作
public void demo1(){
jdbcTemplate.update("insert into account values (null,?,?)", "何菊花",10000d);
}
// 修改操作
public void demo2(){
jdbcTemplate.update("update account set name = ? ,money = ? where id = ?", "何巨涛",2000d,6);
}
// 删除操作
public void demo3(){
jdbcTemplate.update("delete from account where id = ?", 6);
}
3.2 查询操作
3.2.1 查询某个属性
@Test
// 查询操作:
public void demo4(){
String name = jdbcTemplate.queryForObject("select name from account where id = ?", String.class, 5);
System.out.println(name);
}
@Test
// 统计查询
public void demo5(){
Long count = jdbcTemplate.queryForObject("select count(*) from account", Long.class);
System.out.println(count);
}
3.2.2 查询返回对象或集合
实体类
public class Account {
private Integer id;
private String name;
private Double money;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Double getMoney() {
return money;
}
public void setMoney(Double money) {
this.money = money;
}
@Override
public String toString() {
return "Account [id=" + id + ", name=" + name + ", money=" + money + "]";
}
}
// 封装到一个对象中
public void demo6(){
Account account = jdbcTemplate.queryForObject("select * from account where id = ?", new MyRowMapper(), 5);
System.out.println(account);
}
class MyRowMapper implements RowMapper<Account>{
@Override
public Account mapRow(ResultSet rs, int rowNum) throws SQLException {
Account account = new Account();
account.setId(rs.getInt("id"));
account.setName(rs.getString("name"));
account.setMoney(rs.getDouble("money"));
return account;
}
}
// 查询多条记录
public void demo7(){
List<Account> list = jdbcTemplate.query("select * from account", new MyRowMapper());
for (Account account : list) {
System.out.println(account);
}
}
四.Spring事务管理
4.1 事务知识的回顾
- 事务:逻辑上的一组操作,组成这组操作的各个单元,要么全都成功,要么全都失败。
4.1.1 事务的特性
- 原子性:事务不可分割
- 一致性:事务执行前后数据完整性保持一致
- 隔离性:一个事务的执行不应该受到其他事务的干扰
- 持久性:一旦事务结束,数据就持久化到数据库
4.1.2 如果不考虑隔离性引发安全性问题
- 读问题
- 脏读 :一个事务读到另一个事务未提交的数据
- 不可重复读 :一个事务读到另一个事务已经提交的update的数据,导致一个事务中多次查询结果不一致
- 虚读、幻读 :一个事务读到另一个事务已经提交的insert的数据,导致一个事务中多次查询结果不一致。
- 写问题
- 丢失更新
4.1.3 解决读问题
- 设置事务的隔离级别
- Read uncommitted :未提交读,任何读问题解决不了。
- Read committed :已提交读,解决脏读,但是不可重复读和虚读有可能发生。
- Repeatable read :重复读,解决脏读和不可重复读,但是虚读有可能发生。
- Serializable :解决所有读问题。
4.2 Spring事务管理的API
4.2.1 PlatformTransactionManager:平台事务管理器
- 平台事务管理器:接口,是Spring用于管理事务的真正的对象。
- DataSourceTransactionManager :底层使用JDBC管理事务
- HibernateTransactionManager :底层使用Hibernate管理事务
4.2.2 TransactionDefinition :事务定义信息
- 事务定义:用于定义事务的相关的信息,隔离级别、超时信息、传播行为、是否只读
4.2.3 TransactionStatus:事务的状态
- 事务状态:用于记录在事务管理过程中,事务的状态的对象。
4.2.4 事务管理的API的关系:
- Spring进行事务管理的时候,首先平台事务管理器根据事务定义信息进行事务的管理,在事务管理过程中,产生各种状态,将这些状态的信息记录到事务状态的对象中。
4.3 Spring的事务的传播行为
Spring中提供了七种事务的传播行为,A代表被调用者,B代表调用者
4.3.1 保证多个操作在同一个事务中
PROPAGATION_REQUIRED
:默认值,如果A中有事务,使用A中的事务,如果A没有,创建一个新的事务,将操作包含进来
PROPAGATION_SUPPORTS
:支持事务,如果A中有事务,使用A中的事务。如果A没有事务,不使用事务。
PROPAGATION_MANDATORY
:如果A中有事务,使用A中的事务。如果A没有事务,抛出异常。
4.3.2 保证多个操作不在同一个事务中
PROPAGATION_REQUIRES_NEW
:如果A中有事务,将A的事务挂起(暂停),创建新事务,只包含自身操作。如果A中没有事务,创建一个新事务,包含自身操作。
PROPAGATION_NOT_SUPPORTED
:如果A中有事务,将A的事务挂起。不使用事务管理。
PROPAGATION_NEVER
:如果A中有事务,报异常。
4.3.3
嵌套式事务
PROPAGATION_NESTED
:嵌套事务,如果A中有事务,按照A的事务执行,执行完成后,设置一个保存点,执行B中的操作,如果没有异常,执行通过,如果有异常,可以选择回滚到最初始位置,也可以回滚到保存点。
4.4 搭建转账环境
①创建表
create table account(
id int primary key auto_increment,
name varchar(20),
money double
);
②创建Service的接口和实现类
/**
* 转账的业务层的接口
*
*/
public interface AccountService {
public void transfer(String from,String to,Double money);
}
/**
* 转账的业务层的实现类
*
*/
public class AccountServiceImpl implements AccountService {
// 注入DAO:
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
/**
* from:转出账号
* to:转入账号
* money:转账金额
*/
public void transfer( String from, String to, Double money) {
accountDao.outMoney(from, money);
// int d = 1/0;
accountDao.inMoney(to, money);
}
}
③创建DAO的接口和实现类,这里的实现类继承了JdbcDaoSupport,需要在配置中增加datasoure属性即可获得jdbc模板
/**
* 转账的DAO的接口
*
*/
public interface AccountDao {
public void outMoney(String from ,Double money);
public void inMoney(String to ,Double money);
}
/**
* 转账的DAO的实现类
*
*/
public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao {
@Override
public void outMoney(String from, Double money) {
this.getJdbcTemplate().update("update account set money = money - ? where name = ?", money,from);
}
@Override
public void inMoney(String to, Double money) {
this.getJdbcTemplate().update("update account set money = money + ? where name = ?", money ,to);
}
}
④配置 jdbc.properties
jdbc.driverClass=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql:///mytest
jdbc.username=root
jdbc.password=root
⑤配置 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:context="http://www.springframework.org/schema/context"
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/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标签引入的 -->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!-- 配置C3P0连接池=============================== -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driverClass}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="user" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!-- 配置Service============= -->
<bean id="accountService" class="spring.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"/>
</bean>
<!-- 配置DAO================= -->
<bean id="accountDao" class="spring.dao.impl.AccountDaoImpl">
<property name="dataSource" ref="dataSource"/>
</bean>
</beans>
⑥测试类
/**
* 测试转账的环境
*
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class test {
@Resource(name="accountService")
private AccountService accountService;
@Test
public void demo1(){
accountService.transfer("张三", "李四", 1000d);
System.out.println("OK");
}
}
如上只是实现了转账的业务逻辑,如果直接应用还会存在问题,比如转账过程中遇到了停电断网等问题导致整个
业务只完成了一半(完成了扣钱),加钱操作还没完成。
解决问题方式:为转账业务添加事务,出现异常进行回滚操作
4.5 Spring的事务管理:一类:编程式事务(需要手动编写代码)
① 修改转账环境中的业务实现类
/**
* 转账的业务层的实现类
*
*/
public class AccountServiceImpl implements AccountService {
// 注入DAO:
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
// 注入事务管理的模板
private TransactionTemplate trsactionTemplate;
public void setTrsactionTemplate(TransactionTemplate trsactionTemplate) {
this.trsactionTemplate = trsactionTemplate;
}
@Override
/**
* from:转出账号
* to:转入账号
* money:转账金额
*/
public void transfer(final String from, final String to, final Double money) {
trsactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
accountDao.outMoney(from, money);
int d = 1/0;
accountDao.inMoney(to, money);
}
});
}
}
⑤配置平台事务管理器
<!-- 配置平台事务管理器============================= -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
⑥ Spring提供了事务管理的模板类
4.6 Spring的事务管理:二类:声明式事务管理(通过配置实现)---AOP
4.6.1 XML方式的声明式事务管理
① 还原AccountService的转账环境
/**
* 转账的业务层的实现类
* @author jt
*
*/
public class AccountServiceImpl implements AccountService {
// 注入DAO:
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
/**
* from:转出账号
* to:转入账号
* money:转账金额
*/
public void transfer( String from, String to, Double money) {
accountDao.outMoney(from, money);
// int d = 1/0;
accountDao.inMoney(to, money);
}
}
② 配置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: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/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标签引入的 -->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!-- 配置C3P0连接池=============================== -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driverClass}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="user" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!-- 配置Service============= -->
<bean id="accountService" class="spring.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"/>
</bean>
<!-- 配置DAO================= -->
<bean id="accountDao" class="spring.dao.impl.AccountDaoImpl">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 配置平台事务管理器============================= -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 配置事务的增强=============================== -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!-- 事务管理的规则 -->
<!-- <tx:method name="save*" propagation="REQUIRED" isolation="DEFAULT"/>
<tx:method name="update*" propagation="REQUIRED"/>
<tx:method name="delete*" propagation="REQUIRED"/>
<tx:method name="find*" read-only="true"/> -->
<tx:method name="*" propagation="REQUIRED" read-only="false"/>
</tx:attributes>
</tx:advice>
<!-- aop的配置 -->
<aop:config>
<aop:pointcut expression="execution(* spring.service.impl.AccountServiceImpl.*(..))" id="pointcut1"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut1"/>
</aop:config>
</beans>
4.7 注解方式配置事务
①还原AccountServiceImpl并添加注解
/**
* 转账的业务层的实现类
*
*/
@Transactional(isolation=Isolation.DEFAULT,propagation=Propagation.REQUIRED)
public class AccountServiceImpl implements AccountService {
// 注入DAO:
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
/**
* from:转出账号
* to:转入账号
* money:转账金额
*/
public void transfer( String from, String to, Double money) {
accountDao.outMoney(from, money);
int d = 1/0;
accountDao.inMoney(to, money);
}
}
②配置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: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/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标签引入的 -->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!-- 配置C3P0连接池=============================== -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driverClass}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="user" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!-- 配置Service============= -->
<bean id="accountService" class="spring.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"/>
</bean>
<!-- 配置DAO================= -->
<bean id="accountDao" class="spring.dao.impl.AccountDaoImpl">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 配置平台事务管理器============================= -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 开启注解事务================================ -->
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
五.Mysql与事务
MySQL默认的存储引擎是MyISAM,不支持事务和外键,需要改为支持事务和外键的InnoDB
- 查看mysql 提供了哪些存储引擎: show engines;
- 看某个表用了什么引擎(在显示结果里参数engine后面的就表示该表当前用的存储引擎): show create table 表名
- 查看当前存储引擎:show engines;
- 查看你的mysql当前默认的存储引擎: show variables like '%storage_engine%';
解决方式①:
将my-small.ini另存为my.ini,在[mysqld]最后添加为上default-storage-engine=InnoDB,
重启服务,数据库默认的引擎修改为InnoDB
解决方式②:
更改表的存储引擎:alter table 表名 engine = InnoDB;