一、spring整合jdbc
1.1 spring提供了很多整合dao层的技术
jdbc:org.springframework.jdbc.core.JdbcTemplate
hibernate3.0:org.springframework.orm.hibernate3.HibernateTemplate
mybatis:org.springframework.orm.ibatis.SqlMapClientTemplate
JPA:org.springframework.orm.jpa.JpaTemplate
1.2 JdbcTemplate对象
spring提供了一个JdbcTemplate对象,只用这一个对象进行对数据库的交互和DBUtils中的QueryRunner 非常类似。
测试代码:
package cn.itcast.test;
import java.beans.PropertyVetoException;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;
import org.junit.Test;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import cn.itcast.bean.User;
import com.mchange.v2.c3p0.ComboPooledDataSource;
public class Demo {
@Test
public void testJdbc() throws Exception{
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/springjdbc");
dataSource.setDriverClass("com.mysql.jdbc.Driver");
dataSource.setUser("root");
dataSource.setPassword("root");
JdbcTemplate jt = new JdbcTemplate();
jt.setDataSource(dataSource);
String sql= "select * from user";
List<User> list = jt.query(sql,new BeanPropertyRowMapper<User>(User.class));
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
}
}
1.2.1 导包
按照惯例4+2包,test包(spring-test,spring-aop,junit4),数据库包(c3p0连接池,jdbc驱动,spring-jdbc,spring-tx事务)
1.2.2 编写dao层
package cn.itcast.dao;
import java.util.List;
import cn.itcast.bean.User;
public interface UserDao {
void save(User user);
void delete(Integer id);
void update(User user);
User getById(Integer id);
List<User> getAll();
}
package cn.itcast.dao;
import java.util.List;
import org.springframework.jdbc.core.JdbcTemplate;
import cn.itcast.bean.User;
public class UserDaoImpl implements UserDao {
private JdbcTemplate jt;
@Override
public void save(User user) {
String sql = "insert into user (id,name) values(NULL,?)";
jt.update(sql, user.getName());
}
public JdbcTemplate getJt() {
return jt;
}
public void setJt(JdbcTemplate jt) {
this.jt = jt;
}
@Override
public void delete(Integer id) {
String sql = "delete from user where id = ?";
jt.update(sql,id);
}
@Override
public void update(User user) {
String sql = "update user set name = ? where id = ?";
jt.update(sql, user.getName(),user.getId());
}
@Override
public User getById(Integer id) {
String sql = "select * from user where id = ?";
return jt.queryForObject(sql, User.class);
}
@Override
public List<User> getAll() {
String sql = "select * from user";
return jt.queryForList(sql, User.class);
}
}
这里存在一个这样的依赖关系
UserDaoImpl 依赖JdbcTempalate,JdbcTempalate依赖DataSource,根据这种依赖关系我们可以进行bean的依赖注入配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.2.xsd"
>
<bean name="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/springjdbc"></property>
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="user" value="root"></property>
<property name="password" value="root"></property>
</bean>
<bean name="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<bean name="userDaoImpl" class="cn.itcast.dao.UserDaoImpl">
<property name="jt" ref="jdbcTemplate"></property>
</bean>
</beans>
测试代码:
package cn.itcast.test;
import javax.annotation.Resource;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import cn.itcast.bean.User;
import cn.itcast.dao.UserDao;
import com.mchange.v2.c3p0.ComboPooledDataSource;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class Demo {
@Resource(name="userDaoImpl")
private UserDao ud;
@Test
public void testSave(){
User user = new User();
user.setName("test");
ud.save(user);
}
}
1.2.3 db.properties 外部配置
jdbc.jdbcUrl=jdbc:mysql://localhost:3306/springjdbc
jdbc.driverClass=com.mysql.jdbc.Driver
jdbc.user=root
jdbc.password=root
<context:property-placeholder location="classpath:db.properties"/>
<bean name="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property>
<property name="driverClass" value="${jdbc.driverClass}"></property>
<property name="user" value="${jdbc.user}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
这样配置起来就比较灵活了
二、spring中aop事务
2.1 事务
2.1.1 事务的特性(acid)
原子性(Atomicity):原子性是指事务事务包含的所有操作要么全部成功,要么全部失败,因此事务的操作如果成功就必须要完全应用到数据库,如果操作失败则不能对数据库有任何影响。
一致性(Consistency):是指事务必须是数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。也就是整体状态一致。
拿转账来说,假设用户a和用户b两者的钱加起来一共是5000,那么不管a和b之间如何转账,转几次账,事务结束后两个用户的钱相加起来还得是5000,这就是事务的一致性。
隔离性(Isolation):隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。
即要达到这么一种效果:对于任意两个并发的事务t1和t2,在事务t1看来,t2要么在t1开始之前就已经结束,要么在t1结束之后才开始,这样每个事务都感觉不到有其他事务在并发执行。
持久性(Durability):是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。
如我们在使用JDBC操作数据库时,在提交事务方法后,提示用户事务操作完成,当我们程序执行完成直到看到提示后,就可以认定事务以及正确提交,即使这时候数据库出现了问题,也必须要将我们的事务完全执行完成,否则就会造成我们看到提示事务处理完毕,但是数据库因为故障而没有执行事务的重大错误。
以上是事务的四大特性。现在重点来说明一个事务的隔离性。当多个线程都开启事务操作数据库的数据时,数据库系统要能进行隔离操作,以保证各个线程获取数据的准确性,在介绍数据库提供的各种隔离级别之前,我们先看看如果不考虑事务的隔离性,会发生的几种问题:
1.脏读
脏读是指在一个事务处理过程中读取了另一个未提交的事务中的数据。
当一个事务正在多次修改某个数据,而在这个事务中多次修改都还未提交,这时一个并发的事务来访问该数据,就会造成两个事务得到的数据不一致。例如:a向b转账100元,对应的sql命令如下:
update account set money = money + 100 where name = 'b'
update account set money = money -100 where name ='a'
当执行完第一条sql时,a通知b查看账户,b发现确实钱已经到账,(此时即发生了脏读,按理说这条事务没有执行完,b不能读取这个数据,因为这个数据可能还会回滚),而之后无论第二条sql是否执行,只要该事务不提交,则所有的操作都将回滚,那么b以后再次查看账户时就乎发现钱其实没有转到。
2. 不可重复读
不可重复读是指在数据库中的某个数据,一个事务范围内多次查询却返回了不同的数据值,这是由于在查询间隔,被另个事务修改并提交了。
例如事务T1在读取某一数据,而事务T2立马修改了这个数据并且提交事务给数据库,事务T1再次读取该数据就得到了不同的结果,发送了不可重复读。
不可重复读和脏读的区别是,脏读是某一事务读取了另一个事务未提交的脏数据,而不可重复读则是读取了前一个事务提交的数据。
在某些情况下,不可重复读并不是问题,比如我们多次查询某个数据当然以最后查询得到的结果为主就可以了。但是在另一些情况下就可能发生问题,例如对于同一个数据a和b一次查询就可能不同,这样由于顺序的问题就有可能a和b某一方不愿意而打起来了。
3.虚读(幻读)
幻读是事务非独立执行时发生的一种现象。例如事务T1对一个表中所有的行的某个数据项做了从“1”修改为“2”的操作,这时事务T2又对这个表中插入了一行数据项,而这个数据项的数值还是为“1”并且提交给数据库。而操作事务T1的用户如果再查看刚刚修改的数据,会发现还有一行没有修改,其实这行是从事务T2中添加的,就好像产生幻觉一样,这就是发生了幻读。
幻读和不可重复读都是读取了另一条已经提交的事务(这点就脏读不同),所不同的是不可重复读查询的都是同一个数据项,而幻读针对的是一批数据整体(比如数据的个数)。
现在来看看MySQL数据库为我们提供的四种隔离级别:
① Serializable (串行化):可避免脏读、不可重复读、幻读的发生。
② Repeatable read (可重复读):可避免脏读、不可重复读的发生。
③ Read committed (读已提交):可避免脏读的发生。
④ Read uncommitted (读未提交):最低级别,任何情况都无法保证。
以上四种隔离级别最高的是Serializable级别,最低的是Read uncommitted级别,当然级别越高,执行效率就越低。像Serializable这样的级别,就是以锁表的方式(类似于Java多线程中的锁)使得其他的线程只能在锁外等待,所以平时选用何种隔离级别应该根据实际情况。在MySQL数据库中默认的隔离级别为Repeatable read (可重复读)。
所以当一个事务异常后要及时提交或者关闭回滚,不然会导致锁表锁库 不释放。影响效率。(sql server 中好像有这样的情况)
2.2 spring中对事务的封装
spring封装了事务的统一操作,包括打开事务,提交事务和回滚事务。还提供了一个事务操作对象接口,来适配不同的数据库访问框架。
2.2.1.事务管理器
PlatformTransactionManager接口,分别有DataSourceTransactionManager和HibernateTransactionManager
在spring的事务管理中,TransactionManager是最核心的对象。
2.2.2 管理事务的属性
事务的隔离级别(前边四个),是否只读,事务的传播行为。
2.3 spring配置事务管理
2.3.1 编码模拟
添加一个转账场景 dao层
package cn.itcast.dao;
public interface AccountDao {
void add(int money,String name);
void subtract(int money,String name);
}
package cn.itcast.dao;
import org.springframework.jdbc.core.JdbcTemplate;
public class AccountImpl implements AccountDao {
private JdbcTemplate jt;
public void setJt(JdbcTemplate jt) {
this.jt = jt;
}
@Override
public void add(int money, String name) {
String sql = "update user set money = money + ? where name = ?";
jt.update(sql,money,name);
}
@Override
public void subtract(int money, String name) {
String sql = "update user set money = money -? where name = ?";
jt.update(sql,money,name);
}
}
service层
package cn.itcast.service;
import cn.itcast.dao.AccountDao;
public class AccountService {
private AccountDao ad;
public AccountDao getAd() {
return ad;
}
public void setAd(AccountDao ad) {
this.ad = ad;
}
public void transfer(int money,String from,String to){
ad.subtract(money, from);
int a = 1/0;
ad.add(money, to);
}
}
配置xml
<bean name="accountImpl" class="cn.itcast.dao.AccountImpl">
<property name="jt" ref="jdbcTemplate"></property>
</bean>
<bean name="accountService" class="cn.itcast.service.AccountService">
<property name="ad" ref="accountImpl"></property>
</bean>
测试
@Resource(name="accountService")
private AccountService as;
@Test
public void testAccount(){
as.transfer(100, "zhangsan", "lisi");
}
运行后跑出异常,没有事务,导致zhangsan 少了100,而lisi 并没有多加100。
接下来配置事务管理
<bean name="accountService" class="cn.itcast.service.AccountService">
<property name="ad" ref="accountImpl"></property>
<!-- service层引用事务模板来管理事务 -->
<property name="tt" ref="transactionTemplate"></property>
</bean>
<!--配置事务核心管理器 -->
<bean name="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置事务模板 -->
<bean name="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="transactionManager"></property>
</bean>
使用模板来管理事务
package cn.itcast.service;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
import cn.itcast.dao.AccountDao;
public class AccountService {
private TransactionTemplate tt;
private AccountDao ad;
public void setTt(TransactionTemplate tt) {
this.tt = tt;
}
public AccountDao getAd() {
return ad;
}
public void setAd(AccountDao ad) {
this.ad = ad;
}
public void transfer(final int money,final String from,final String to){
tt.execute( new TransactionCallbackWithoutResult(){
@Override
protected void doInTransactionWithoutResult(TransactionStatus arg0) {
ad.subtract(money, from);
// int a = 1/0;
ad.add(money, to);
}
});
}
}
可以测试当抛出了异常 转账就不会提交 就不会出现转账错误的情况。
2.3 spring中aop事务配置
2.3.1 导包
aop,aspect,aop联盟包,weaving包
2.3.2 导入名称空间
xmlns:
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation:
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.2.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.2.xsd
2.3.3 配置
这是正常使用spring-jdbc的配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans" xmlns:p="http://www.springframework.org/schema/p"
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-4.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.2.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.2.xsd">
<context:property-placeholder location="classpath:db.properties" />
<bean name="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property>
<property name="driverClass" value="${jdbc.driverClass}"></property>
<property name="user" value="${jdbc.user}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<bean name="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<bean name="accountDao" class="cn.itcast.dao.AccountDaoImpl">
<property name="jt" ref="jdbcTemplate"></property>
</bean>
<bean name="accountService" class="cn.itcast.service.AccountServiceImpl">
<property name="ad" ref="accountDao"></property>
</bean>
</beans>
添加事务管理配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans" xmlns:p="http://www.springframework.org/schema/p"
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-4.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.2.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.2.xsd">
<!--事务核心管理器 -->
<bean name="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置事务通知 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!-- 以方法为单位,指定方法应用说明事务属性 isolaction:隔离级别 propagation:传播行为 read-only:是否只读(这里拦截方法名没transfer的方法,也可以使用通配符配置) -->
<tx:method name="transfer" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false" />
</tx:attributes>
</tx:advice>
<!-- 配置织入 -->
<aop:config>
<!--配置切点表达式 ,要切入哪些方法 -->
<aop:pointcut expression="execution(* cn.itcast.service.*ServiceImpl.*(..))"
id="txPc" />
<!-- 配置切面 advice-ref:通知的名称 pointcut-ref:切点的名称 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPc" />
</aop:config>
</beans>
注意:这里使用的Mysql数据库的引擎一定要是 InnoDB ,有时候默认是 MyISAM,而MyISAM是不支持事务的,这会导致你配置成功之后事务一直不会滚,坑爹的。
可以右键表设计里边修改:
测试代码:
package cn.itcast.test;
import cn.itcast.service.AccountService;
import cn.itcast.service.AccountServiceImpl;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class Demo {
@Resource(name="accountService")
private AccountService as;
@Test
public void testAccount(){
as.transfer(100, "zhangsan", "lisi");
}
}
若跑出异常,转账则会回滚。
2.4 注解使用方式
2.4.1 配置xml
<!--事务核心管理器 -->
<bean name="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<tx:annotation-driven/>
业务层使用注解
@Transactional(isolation=Isolation.REPEATABLE_READ,propagation=Propagation.REQUIRED,readOnly=false)
public void transfer(int money, String from, String to) {
ad.subtract(money, from);
//int a = 1 / 0;
ad.add(money, to);
}
三、总结
这次我们使用spring-aop方式配置全局事务管理,开发起来更加的方便和轻松。