JdbcTemplate
概述
为了使JDBC更加易于使用,Spring在JDBC API上定义了一个抽象层,以此建立一个JDBC存取框架。
作为Spring JDBC框架的核心,JDBC模板的设计目的是为不同类型的JDBC操作提供模板方法,通过这种方式,可以在尽可能保留灵活性的情况下,将数据库存取的工作量降到最低。
可以将Spring的JdbcTemplate看作是一个小型的轻量级持久化层框架,和我们之前使用过的DBUtils风格非常接近。
环境准备
1) IOC容器所需要的JAR包
commons-logging-1.1.1.jar
spring-beans-4.0.0.RELEASE.jar
spring-context-4.0.0.RELEASE.jar
spring-core-4.0.0.RELEASE.jar
spring-expression-4.0.0.RELEASE.jar
2) JdbcTemplate所需要的JAR包
spring-jdbc-4.0.0.RELEASE.jar
spring-orm-4.0.0.RELEASE.jar
spring-tx-4.0.0.RELEASE.jar
3) 数据库驱动和数据源
druid-1.1.9.jar
mysql-connector-java-5.1.7-bin.jar
有点简单,不多解释,直接上代码
package jdbctemplate;
public class Student
{
public Integer sid;
public String name;
public Integer age;
//下面省略get和set方法
}
<?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">
<!-- 加载资源文件 -->
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location" value="Druid.properties"></property>
</bean>
<!-- 创建数据源 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!-- 根据数据源创建jdbcTempLate对象 -->
<bean id="jdbcTempLate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
</beans>
package jdbctemplate;
import java.util.ArrayList;
import java.util.List;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
public class Test
{
ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("jdbc.xml");
JdbcTemplate jdbcTemplate = ac.getBean("jdbcTempLate", JdbcTemplate.class);
@org.junit.Test
public void test()
{
//插入单条数据
// String sql1="insert into stu values(null,'张三',22)";
// String sql2="insert into stu values(null,?,?)";
// jdbcTemplate.update(sql1);
// jdbcTemplate.update(sql2, "李四",23);
}
@org.junit.Test
public void testBatchUpdate()
{
//批量添加
String sql ="insert into stu values(null,?,?)";
List<Object[]> list=new ArrayList<Object[]>();
list.add(new Object[] {"a1",24});
list.add(new Object[] {"a2",25});
list.add(new Object[] {"a3",26});
jdbcTemplate.batchUpdate(sql, list);
/*
* 批量删除
* 这里执行是不会报错的,但是任何的一条数据都没有被删除
* 这里使用的是preparedstatement进行预处理的,那么预处理使用的语句就是setString的方法
* 在java里面,"a1,a2,a3"是字符串,外面的""是标识的,字符串的内容是a1,a2,a3,
* 那么就行预处理的setstring方法中,会自动为字符串添加两个'',
* 那么最后拼接的sql语句是delete from stu where name in ('a1,a2,a3')
* 这样执行就什么都没有被删除
*/
// String sql2="delete from stu where name in (?)";
// String eids="'a1','a2','a3'";
// jdbcTemplate.update(sql2, eids);
//所以这个批量删除可以写成statement的形式
// String eids1="'a1','a2','a3'";
// String sql3="delete from stu where name in ("+eids1+")";
// jdbcTemplate.update(sql3);
//还有模糊查询,这里预处理后就变成select * from str where name like '%'a'%'
//明显不可以执行,但是实在是想预处理的话,可以设置成这样
//String sql4="select * from str where name like concat('%',?,'%')";
//这样concat函数就将参数设置进去而且它本来就是需要字符串参数
// String text="a";
// String sql4="select * from str where name like '%?%'";
// jdbcTemplate.update(sql4,text);
//所以批量删除,批量修改,模糊查询都是不能使用preparedstatement的通配符进行赋值的
//因为赋值后是有问题的,有些还是直接语句报错
}
@org.junit.Test
public void testQueryForObject()
{
// jdbcTemplate.queryForObject(sql, requiredType);用来获取单个的值
// jdbcTemplate.queryForObject(sql, rowMapper);rowMapper就是行映射,这是用来获取单条数据
String sql="select sid,name,age from stu where sid = ? ";
RowMapper<Student> rowMapper=new BeanPropertyRowMapper<Student>(Student.class);
Student student = jdbcTemplate.queryForObject(sql, new Object[] {15}, rowMapper);
System.out.println(student);
String sql1="select count(*) from stu";
Integer count = jdbcTemplate.queryForObject(sql1, Integer.class);
System.out.println(count);
String sql2="select sid,name,age from stu";
RowMapper<Student> rowMapper1=new BeanPropertyRowMapper<>(Student.class);
List<Student> query = jdbcTemplate.query(sql2, rowMapper1);
for (Student student2 : query)
{
System.out.println(student2);
}
}
}
声明式事务管理
事务概述
简单看一下概述
- 在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框架存取数据库
事务的传播行为
当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。
事务的传播行为可以由传播属性指定。Spring定义了7种类传播行为。
事务的隔离级别
数据库系统必须具有隔离并发运行各个事务的能力,使它们不会相互影响,避免各种并发问题。一个事务与其他事务隔离的程度称为隔离级别。SQL标准中规定了多种事务隔离级别,不同隔离级别对应不同的干扰程度,隔离级别越高,数据一致性就越好,但并发性越弱。
- 读未提交:READ UNCOMMITTED
允许Transaction01读取Transaction02未提交的修改。 - 读已提交:READ COMMITTED
要求Transaction01只能读取Transaction02已提交的修改。 - 可重复读:REPEATABLE READ
确保Transaction01可以多次从一个字段中读取到相同的值,即Transaction01执行期间禁止其它事务对这个字段进行更新。 - 串行化:SERIALIZABLE
确保Transaction01可以多次从一个表中读取到相同的行,在Transaction01执行期间,禁止其它事务对这个表进行添加、更新、删除操作。可以避免任何并发问题,但性能十分低下。
在Spring中指定事务隔离级别
- 注解
用@Transactional注解声明式地管理事务时可以在@Transactional的isolation属性中设置隔离级别 - XML
在Spring 2.x事务通知中,可以在tx:method元素中指定隔离级别
触发事务回滚的异常
默认情况:捕获到RuntimeException或Error时回滚,而捕获到编译时异常不回滚。
设置途经
- 注解@Transactional 注解
① rollbackFor属性:指定遇到时必须进行回滚的异常类型,可以为多个
② noRollbackFor属性:指定遇到时不回滚的异常类型,可以为多个 - XML
在Spring 2.x事务通知中,可以在tx:method元素中指定回滚规则。如果有不止一种异常则用逗号分隔。
事务的超时和只读属性
由于事务可以在行和表上获得锁,因此长事务会占用资源,并对整体性能产生影响。
如果一个事务只读取数据但不做修改,数据库引擎可以对这个事务进行优化。
超时事务属性:事务在强制回滚之前可以保持多久。这样可以防止长期运行的事务占用资源。
只读事务属性: 表示这个事务只读取数据但不更新数据, 这样可以帮助数据库引擎优化事务。
设置
- 注解
@Transaction注解 - XML
在Spring 2.x事务通知中,超时和只读属性可以在< tx:method>元素中进行指定
没有例子是没有灵魂的
package book;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
@Controller
public class BookController
{
@Autowired
private BookService service;
@Autowired
private CashierService cashierService;
public void buybook()
{
service.buybook("1","1001" );
}
public void checkOut()
{
List<String> bids=new ArrayList<String>();
bids.add("1");
bids.add("2");
cashierService.checkOut("1001", bids);
}
}
package book;
public interface BookDao
{
Integer selectPrice(String bid);
void updateSt(String bid);
void updateBalance(String uid,Integer price);
}
package book;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
@Repository
public class BookDaoImpl implements BookDao
{
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public Integer selectPrice(String bid)
{
String sql="select price from book where bid= ?";
Integer price = jdbcTemplate.queryForObject(sql, new Object[] {bid}, Integer.class);
return price;
}
@Override
public void updateSt(String bid)
{
String sql="select st from stock where sid=?";
Integer st = jdbcTemplate.queryForObject(sql, new Object[] {bid}, Integer.class);
if(st>0)
{
sql="update stock set st=st-1 where sid =?";
jdbcTemplate.update(sql, bid);
}
else
{
throw new RuntimeException("库存不够");
}
}
@Override
public void updateBalance(String uid,Integer price)
{
String sql="select balance from money where uid= ?";
Integer balance = jdbcTemplate.queryForObject(sql, new Object[] {uid}, Integer.class);
if(balance < price)
{
throw new RuntimeException("余额不够");
}
else
{
sql="update money set balance =balance - ? where uid =?";
jdbcTemplate.update(sql, price,uid);
}
}
}
package book;
public interface BookService
{
void buybook(String bid,String uid);
}
package book;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Service
@Transactional
public class BookServiceImpl implements BookService
{
@Autowired
private BookDao dao;
/*
* @Transactional:对方法中的所有操作作为一个事务进行管理
* 在方法上就只对方法有效
* 在类上就对类中的所有方法都有效
* @Transactional中可以设置的属性
* propagation:是A方法和B方法中都有事务,当A调用B时,会将A中的事务传播给B方法,
* B方法对于A方法传来的事务的处理方式就是事务的传播行为
* 取值常用的有:
* Propagation.REQUIRES
* 表示事务B必须使用调用者A的事务(默认)
*
* Propagation.REQUIRES_NEW
* 表示将调用者的事务挂起,不使用调用者的事务,自己创建新的事务自己使用
*
* isolation:指事务的隔离级别
* 读未提交:出现脏读,就是没有提交的数据可以读,对于字段来说
* 读已提交:出现不可重复读,就是读已经提交的数据,但是你读的时候别人可能同时在修改,那么你两次读的数据可能不一样,对于字段来说
* 可重复读:出现幻读,就是你正在读的数据不允许别人就行修改,但是别人还是可以修改整个表的其他数据,比如增加一条记录,对于表来说
* 串行化:相当于单线程,性能低,消耗大
*
* timeout:在事务强制回滚前最多可以执行等待的时间
* 比如淘宝的秒杀互动,一个用户的请求占用了服务器的一个连接
* 但是在请求后这个用户的事务因为某个地方卡住了,导致后面的
* 执行不了,那么这个timeout就是设置这个事务多少时间后还没结束的
* 话就直接回滚
* 这样设置:timeout = 2,两秒后回滚
*
* readOnly:指定当前的事务中的一系列的操作是否是只读
* 在数据库的事务操作中,如果一个事务对某一条数据进行访问时
* 那么数据库就会为这条数据加上一个锁,数据锁住后其他的事务是不能访问这条数据的
* 必须等当前事务做完,然后释放锁才能访问
* 而这个readOnly标签就是spring通知数据库,这个事务只有读的操作
* 不要加上锁,这样提高性能
* 设置readOnly=True后,无论事务有没有写的操作,都不加锁,所以事务有写的操作时一定不能加这个属性
*
* rollbackFor|rollbackForClassName|noRollbackFor|noRollbackForClassName
* rollbackFor:本来程序是抛出异常就会回滚,闲杂这个rollbackFor是指当程序抛出指定的异常就会回滚,不是指定的异常就不会回滚
* 参数是一个class类型的数组,里面写上异常类型,例如rollbackFor = {NullPointerException.class
* noRollbackFor就是不因为什么回滚
*/
@Transactional(propagation = Propagation.REQUIRED,timeout = 2,rollbackFor = {NullPointerException.class})
public void buybook(String bid, String uid)
{
Integer price = dao.selectPrice(bid);
dao.updateSt(bid);
dao.updateBalance(uid, price);
}
}
package book;
import java.util.List;
//结账的功能
public interface CashierService
{
void checkOut(String uid,List<String> bids);
}
package book;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class CashierServiceImpl implements CashierService
{
@Autowired
private BookService bookService;
/*
* 如果没有加这个,下面的checkout方法中的买多本书就不是一个事务,
* 有可能前面够钱买的书买到了,后面不够前的书买不到,这个方法中调用了
* buybook方法,事务的具体运行还得看buybook的事务设置
*/
@Transactional
public void checkOut(String uid, List<String> bids)
{
for (String bid : bids)
{
bookService.buybook(bid, uid);
}
}
}
package book;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test
{
public static void main(String[] args)
{
ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("book.xml");
BookController bookController = ac.getBean("bookController", BookController.class);
// bookController.buybook();
bookController.checkOut();
}
}
<?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: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-4.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd">
<context:component-scan base-package="book"></context:component-scan>
<!-- 加载资源文件 -->
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location" value="Druid.properties"></property>
</bean>
<!-- 创建数据源 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!-- 根据数据源创建jdbcTempLate对象 -->
<bean id="jdbcTempLate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置事务管理器,事务管理器的存在是管理连接对象所产生的事务,所以要给它数据源 -->
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 这个是注解驱动,增加这个标签后,在类或者方法里面增加事物的注解才有效 -->
<!-- 所以就是开启注解驱动,对事务的相关注解进行扫描,解释含义并且执行功能 -->
<!-- transaction-manager是设置事务管理器,默认的事务管理器是transactionManager -->
<!-- 然后在方法或者类里面增加@Transactional注解,就是方法里面创建一个事务或者类里面的所有方法都开启事务 -->
<tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>
</beans>
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:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
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-4.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd">
<context:component-scan base-package="book"></context:component-scan>
<!-- 加载资源文件 -->
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location" value="Druid.properties"></property>
</bean>
<!-- 创建数据源 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!-- 根据数据源创建jdbcTempLate对象 -->
<bean id="jdbcTempLate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置事务管理器,事务管理器的存在是管理连接对象所产生的事务,所以要给它数据源 -->
<!-- 不管是注解的方式配置事务还是xml的方式配置事务,都是需要事务管理器 -->
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置切入点表达式 -->
<aop:config>
<aop:pointcut expression="execution( * book.*.*(..))" id="pointCut"/>
<!-- 将事务通知和切入点表达式关联起来 -->
<aop:advisor advice-ref="tx" pointcut-ref="pointCut"/>
</aop:config>
<!-- 配置事务通知 -->
<tx:advice id="tx" transaction-manager="dataSourceTransactionManager">
<tx:attributes>
<!-- 在设置好的切入点表达式再次进行事务设置 -->
<tx:method name="buyBook"/>
<tx:method name="checkOut"/>
<!-- 如果方法太多,就进行通配符进行实现 -->
<tx:method name="select*" read-only="true"/> <!-- 这个是对于所有的select开头的方法都进行设置 -->
<tx:method name="update*"/> <!-- 这个是对于所有的update开头的方法都进行设置 -->
<tx:method name="*"/> <!-- 所有的方法 -->
</tx:attributes>
</tx:advice>
</beans>