JdbcTemplate
-
为了使JDBC更加易于使用,Spring在JDBC API上定义了一个抽象层,以此建立一个JDBC存取框架。
-
作为Spring JDBC框架的核心,JDBC模板的设计目的是为不同类型的JDBC操作提供模板方法,通过这种方式,可以在尽可能保留灵活性的情况下,将数据库存取的工作量降到最低。
-
可以将Spring的JdbcTemplate看作是一个小型的轻量级持久化层框架,和我们之前使用过的DBUtils风格非常接近。
①,创建连接数据库基本信息属性文件db.properties
jdbc.driver = com.mysql.jdbc.Driver
jdbc.url = jdbc:mysql://localhost:3306/test
jdbc.username = root
jdbc.password = root
②,在Spring配置文件中配置相关的bean,jdbc.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 引入属性文件 -->
<context:property-placeholder location="db.properties"/>
<!-- 创建数据源 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!-- 通过数据源配置JdbcTemplate -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
</beans>
- 增删改:
- 批量增删改
/**
* Object[]封装了SQL语句每一次执行时所需要的参数
* List集合封装了SQL语句多次执行时的所有参数
*/
JdbcTemplate.batchUpdate(String, List<Object[]>)//
- 查询单行
JdbcTemplate.queryForObject(String, RowMapper<Department>, Object...)
-
查询多行
RowMapper对象依然可以使用
BeanPropertyRowMapperJdbcTemplate.query(String, RowMapper<Department>, Object...)
-
查询单一值
JdbcTemplate.queryForObject(String, Class, Object...)
- 示例代码:
public class JdbcTest {
ApplicationContext ac = new ClassPathXmlApplicationContext("jdbc.xml");
JdbcTemplate jdbcTemplae = ac.getBean("jdbcTemplate", JdbcTemplate.class);
/**
* 单个增删改
*/
@Test
public void updateDemo(){
// jdbcTemplae.update("insert into user values (null,'Tommey01',123,'上海',15131315123,null)");
// String sql = "insert into user values(null,?,?,?,15131315123,null)";
String sql = "delete from user where address like ?";//"长沙"在?占位符中自动转成'长沙’,所以不能使用in操作
jdbcTemplae.update(sql,"%地址2%");
}
/**
* 批量增删改
*/
@Test
public void batchUpdateDemo(){
String sql = "insert into user values (null,?,?,?,15131315123,null)";
List<Object[]> list = new ArrayList<>();
list.add(new Object[]{"名字1","密码1","地址1"});
list.add(new Object[]{"名字2","密码2","地址2"});
list.add(new Object[]{"名字3","密码3","地址3"});
jdbcTemplae.batchUpdate(sql,list);
}
@Test
public void queryForObjectDemo(){
String sql = "select id,name,password,phone from user where id = ?";
RowMapper<User> rowMapper = new BeanPropertyRowMapper<>(User.class);
User user = jdbcTemplae.queryForObject(sql, new Object[]{14}, rowMapper);//将列名(字段名或字段名的别名)与属性名进行映射
System.out.println(user);
String sql1 = "select count(1) from user";
Integer count = jdbcTemplae.queryForObject(sql1, Integer.class);
System.out.println(count);
}
@Test
public void queryDemo(){
String sql = "select id,name,password,phone from user";
RowMapper<User> rowMapper = new BeanPropertyRowMapper<>(User.class);
List<User> query = jdbcTemplae.query(sql, rowMapper);
System.out.println(Arrays.toString(query.toArray()));
}
}
声明式事务管理
-
在JavaEE企业级开发的应用领域,为了保证数据的完整性和一致性,必须引入数据库事务的概念,所以事务管理是企业级应用程序开发中必不可少的技术。
-
事务就是一组由于逻辑上紧密关联而合并成一个整体(工作单元)的多个数据库操作,这些操作要么都执行,要么都不执行。
-
事务的四个关键属性(ACID)
①,
原子性(atomicity)
:“原子”的本意是“不可再分”,事务的原子性表现为一个事务中涉及到的多个操作在逻辑上缺一不可。事务的原子性要求事务中的所有操作要么都执行,要么都不执行。②,
一致性(consistency)
:“一致”指的是数据的一致,具体是指:所有数据都处于满足业务规则的一致性状态。一致性原则要求:一个事务中不管涉及到多少个操作,都必须保证事务执行之前数据是正确的,事务执行之后数据仍然是正确的。如果一个事务在执行的过程中,其中某一个或某几个操作失败了,则必须将其他所有操作撤销,将数据恢复到事务执行之前的状态,这就是回滚。③,
隔离性(isolation)
:在应用程序实际运行过程中,事务往往是并发执行的,所以很有可能有许多事务同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。隔离性原则要求多个事务在并发执行过程中不会互相干扰。④,
持久性(durability)
:持久性原则要求事务执行完成后,对数据的修改永久的保存下来,不会因各种系统错误或其他意外情况而受到影响。通常情况下,事务对数据的修改应该被写入到持久化存储器中。
-
编程式事务管理
-
使用原生的JDBC API进行事务管理
①,获取数据库连接Connection对象
②,取消事务的自动提交
③,执行操作
④,正常完成操作时手动提交事务
⑤,执行失败时回滚事务
⑥,关闭相关资源
-
评价
使用原生的JDBC API实现事务管理是所有事务管理方式的基石,同时也是最典型 的编程式事务管理。编程式事务管理需要将事务管理代码嵌入到业务方法中来控制事务 的提交和回滚。在使用编程的方式管理事务时,必须在每个事务操作中包含额外的事务 管理代码。相对于核心业务而言,事务管理的代码显然属于非核心业务,如果多个模块 都使用同样模式的代码进行事务管理,显然会造成较大程度的代码冗余。
-
-
声明式事务管理
-
大多数情况下声明式事务比编程式事务管理更好:它将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理。
-
事务管理代码的固定模式作为一种横切关注点,可以通过AOP方法模块化,进而借助Spring AOP框架实现声明式事务管理。
-
Spring在不同的事务管理API之上定义了一个抽象层,通过配置的方式使其生效,从而让应用程序开发人员不必了解事务管理API的底层实现细节,就可以使用Spring的事务管理机制。
-
Spring既支持编程式事务管理,也支持声明式的事务管理。
-
-
Spring提供的事务管理器
-
Spring从不同的事务管理API中抽象出了一整套事务管理机制,让事务管理代码从特定的事务技术中独立出来。开发人员通过配置的方式进行事务管理,而不必了解其底层是如何实现的。
-
Spring的核心事务管理抽象是它为事务管理封装了一组独立于技术的方法。无论使用Spring的哪种事务管理策略(编程式或声明式),事务管理器都是必须的。事务管理器可以以普通的bean的形式声明在Spring IOC容器中。
-
-
事务管理器的主要实现
-
DataSourceTransactionManager
:在应用程序中只需要处理一个数据源,而且通过JDBC存取。 -
JtaTransactionManager
:在JavaEE应用服务器上用JTA(Java Transaction API)进行事务管理 -
HibernateTransactionManager
:用Hibernate框架存取数据库
-
- 示例代码:
<!-- 配置事务管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 启用事务注解 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
- 在需要进行事务控制的方法上加注解
@Transactional
-
简介
当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。事务的传播行为可以由传播属性指定。Spring定义了7种类传播行为。
传播属性 | 描述 |
---|---|
REQUIRED | 如果有事务在运行,当前的方法就在这个事务内运行,否则,就启功一个新的事务,并在自己的事务内运行 |
REQUIRED_ NEW | 当前的方法必须启动新事务,并在它自己的事务内运行.如果有事务正在运行,应该将它挂起 |
SUPPORTS | 如果有事务在运行,当前的方法就在这个事务内运行.否则它可以不运行在事务中 |
NOT_ SUPPORTE | 当前的方法不应该运行在事务中.如果有运行的事务,将它挂起 |
MANDATORY | 当前的方法必须运行在事务内部, 如果没有正在运行的事务,就抛出异常 |
NEVER | 当前的方法不应该运行在事务中.如果有运行的事务,就抛出异常 |
NESTED | 如果有事务在运行,当前的方法就应该在这个事务的嵌套事务内运行,否则,就启动一个新的事务,并在它自己的事务内运行. |
- 事务传播属性可以在@Transactional注解的propagation属性中定义。
①,数据库事务并发问题
-
假设现在有两个事务:Transaction01和Transaction02并发执行
-
脏读
- Transaction01将某条记录的AGE值从20修改为30;Transaction02读取了Transaction01更新后的值:30;Transaction01回滚,AGE值恢复到了20;Transaction02读取到的30就是一个无效的值。
-
不可重复读
- Transaction01读取了AGE值为20;Transaction02将AGE值修改为30;Transaction01再次读取AGE值为30,和第一次读取不一致。
-
幻读
- Transaction01读取了STUDENT表中的一部分数据;Transaction02向STUDENT表中插入了新的行;Transaction01读取了STUDENT表时,多出了一些行
②,隔离级别
- 数据库系统必须具有隔离并发运行各个事务的能力,使它们不会相互影响,避免各种并发问题。一个事务与其他事务隔离的程度称为隔离级别。SQL标准中规定了多种事务隔离级别,不同隔离级别对应不同的干扰程度,隔离级别越高,数据一致性就越好,但并发性越弱。
隔离级别 | 说明 |
---|---|
读未提交:READ UNCOMMITTED | 允许Transaction01读取Transaction02未提交的修改 |
读已提交:READ COMMITTED | 要求Transaction01只能读取Transaction02已提交的修改 |
可重复读:REPEATABLE READ | 确保Transaction01可以多次从一个字段中读取到相同的值,即Transaction01执行期间禁止其它事务对这个字段进行更新 |
串行化:SERIALIZABLE | 确保Transaction01可以多次从一个表中读取到相同的行,在Transaction01执行期间,禁止其它事务对这个表进行添加、更新、删除操作。可以避免任何并发问题,但性能十分低下 |
③,在Spring中指定事务隔离级别
- 注解:用
@Transactional
注解声明式地管理事务时可以在@Transactional
的isolation
属性中设置隔离级别
@Transactional(isolation = Isolation.READ_COMMITTED,propagation = Propagation.REQUIRES_NEW)
public void buyShop(String sid,String uid){
}
- XML:在Spring 2.x事务通知中,可以在
<tx:method>
元素中指定隔离级别
-
默认情况
捕获到RuntimeException或Error时回滚,而捕获到编译时异常不回滚。
-
设置途经
-
注解
@Transactional
注解rollbackFor
属性:指定遇到时必须进行回滚的异常类型,可以为多个noRollbackFor
属性:指定遇到时不回滚的异常类型,可以为多个
-
@Transactional(isolation = Isolation.READ_COMMITTED,rollbackFor = {IOException.class, SQLException.class},noRollbackFor = ArithmeticException.class)
public void buyShop(String sid,String uid){
}
-
XML
在Spring 2.x事务通知中,可以在
<tx:method>
元素中指定回滚规则。如果有不止
-
readOnly
:指定当前事务中的一系列的操作是否为只读,若设置为只读,不管事务中有没有写的操作,mysql都会在请求访问数据的时候,不加锁,提高性能,但是如果有写操作的情况,建议一定不能设置只读 -
简介:
由于事务可以在行和表上获得锁,因此长事务会占用资源,并对整体性能产生影响。
如果一个事务只读取数据但不做修改,数据库引擎可以对这个事务进行优化。
超时事务属性:事务在强制回滚之前可以保持多久。这样可以防止长期运行的事务占用资源。
只读事务属性: 表示这个事务只读取数据但不更新数据, 这样可以帮助数据库引擎优化事务。
-
设置:
- 注解
@Transactional(readOnly = true,timeout = 30,propagation = Propagation.REQUIRES_NEW)
public void buyShop(String sid,String uid){
}
-
XML
在Spring 2.x事务通知中,超时和只读属性可以在
<tx:method>
元素中进行指定
<!--配置事务管理器,不管是用注解方式或xml方式配置事务,一定要有DataSourceTransactionManager事务管理器支持 -->
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 开启注解驱动,即对事务相关的注解进行扫描,解析含义并执行功能-->
<!-- <tx:annotation-driven />-->
<!-- 配置事务切面 -->
<aop:config>
<aop:pointcut
expression="execution(* com.service.BookShopServiceImpl.purchase(..))"
id="txPointCut"/>
<!-- 将切入点表达式和事务属性配置关联到一起 -->
<aop:advisor advice-ref="myTx" pointcut-ref="txPointCut"/>
</aop:config>
<!-- 配置基于XML的声明式事务 -->
<tx:advice id="myTx">
<tx:attributes>
<!-- 设置具体方法的事务属性 -->
<tx:method name="find*" read-only="true"/>
<tx:method name="get*" read-only="true"/>
<tx:method name="purchase"
isolation="READ_COMMITTED"
no-rollback-for="java.lang.ArithmeticException,java.lang.NullPointerException"
propagation="REQUIRES_NEW"
read-only="false"
timeout="10"/>
</tx:attributes>
</tx:advice>