Spring框架相关知识点,利用声明式事物管理操作进行数据库的读写操作,
案例
要求
1.向存款表中2号账户添加一条存入200元的信息 2.查询客户表2号账户的余额信息 3.根新客户表2号的余额信息(500+200) 4.根新客户表,使余额为最新。
做法
先创建两个表,存款表 和 客户表
id | addmoney |
---|---|
id | name | money |
---|---|---|
1 | 范冰冰 | 1000.0 |
2 | 杨幂 | 2000.0 |
service
层中使用save
方法调用dao
层,再在dao
层中执行数据库操作。
service
public void save(Addmony addmony) {
//向addmoney表存入500
addmoneyDao.save(addmony);
//根据id查询用户余额信息
Integer id = addmony.getId();
Customer customer = customerDao.findById(id);
//修改余额信息
customer.setMoney(customer.getMoney()+addmony.getAddmoney());
//根系余额信息
customerDao.update(customer);
}
dao1
向存款表添加数据
public void save(Addmony addmony) {
String sql = "insert into addmoney values(?,?)";
Object [] args = {addmony.getId(),addmony.getAddmoney()};
this.jdbcTemplate.update(sql,args);
}
dao2
客户表更改余额信息
public void update(Customer customer) {
String sql = "update customer set money=? where id=?";
Object [] args = {customer.getMoney(),customer.getId()};
this.jdbcTemplate.update(sql,args);
}
这样执行完之后就完成要求,表1 表2 都根新了数据
但是,如果把dao2
中的sql
语句改成
"update customer set aaamoney=? where id=?"
把money
字段写错,这样在根新时会发生错误,但是之前的操作都正常执行了,数据库就变成
id | addmoney |
---|---|
2 | 500.0 |
id | name | money |
---|---|---|
1 | 范冰冰 | 1000.0 |
2 | 杨幂 | 2000.0 |
2号客户杨幂的money
没有加上500,所以需要引入事务,操作要么都成功,要么都
事务的概念
事务是一组操作的执行单元,相对于数据库操作来讲,事务管理的是一组SQL指令,比如增加,修改,删除等,事务的一致性,要求,这个事务内的操作必须全部执行成功,如果在此过程种出现了差错,比如有一条SQL语句没有执行成功,那么这一组操作都将全部回滚
仅用四个词解释事务(ACID)
atomic
(原子性):要么都发生,要么都不发生。consistent
(一致性):数据应该不被破坏。Isolate
(隔离性):用户间操作不相混淆Durable
(持久性):永久保存,例如保存到数据库中等
声明式事务管理
如果你并不需要细粒度的事务控制,你可以使用声明式事务,在Spring中,你只需要在Spring配置文件中做一些配置,即可将操作纳入到事务管理中,解除了和代码的耦合, 这是对应用代码影响最小的选择,从这一点再次验证了Spring关于AOP的概念。当你不需要事务管理的时候,可以直接从Spring配置文件中移除该设置
传播行为
传播行为:定义关于客户端和被调用方法的事物边界
REQUIRED:业务方法需要在一个容器里运行。如果方法运行时,已经处在一个事务中,那么加入到这个事务,否则自己新建一个新的事务。
NOT_SUPPORTED:声明方法不需要事务。如果方法没有关联到一个事务,容器不会为他开启事务,如果方法在一个事务中被调用,该事务会被挂起,调用结束后,原先的事务会恢复执行。
REQUIRESNEW:不管是否存在事务,该方法总汇为自己发起一个新的事务。如果方法已经运行在一个事务中,则原有事务挂起,新的事务被创建。
MANDATORY:该方法只能在一个已经存在的事务中执行,业务方法不能发起自己的事务。如果在没有事务的环境下被调用,容器抛出例外。
SUPPORTS:该方法在某个事务范围内被调用,则方法成为该事务的一部分。如果方法在该事务范围外被调用,该方法就在没有事务的环境下执行。
NEVER:该方法绝对不能在事务范围内执行。如果在就抛例外。只有该方法没有关联到任何事务,才正常执行。
NESTED:如果一个活动的事务存在,则运行在一个嵌套的事务中。如果没有活动事务,则按REQUIRED属性执行。它使用了一个单独的事务,这个事务拥有多个可以回滚的保存点。内部事务的回滚不会对外部事务造成影响。它只对DataSourceTransactionManager事务管理器起效。
例如上面案例中的操作。
声明传播行为REQUIRED
(默认值)也是项目应用最多的传播行为,因为他表示如果业务方法存在一个事务中,则直接使用这个事务。如果业务方法不存在事务,则开启一个新的事务。
隔离级别
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
Read Uncommitted | √ | √ | √ |
Read Committed | × | √ | √ |
Repeatable Read | × | × | √ |
Serializable | × | × | × |
脏读:一个事务读取了另一个事务改写但还未提交的数据,如果这些数据被回滚,则读到的数据是无效的。
不可重复读:在同一事务中,多次读取同一数据返回的结果有所不同。换句话说就是,后续读取可以读到另一事务已提交的更新数据。相反,“可重复读”在同一事务中多次读取数据时,能够保证所读数据一样,也就是,后续读取不能读到另一事务已提交的更新数据。
幻读:一个事务读取了几行记录后,另一个事务插入一些记录,幻读就发生了。再后来的查询中,第一个事务就会发现有些原来没有的记录。
如果使用DEFAULT
默认值,表示数据库采用那么隔离级别,我们就使用哪种隔离级别
声明事务处理(xml方式)
现在spring容器(beans.xml)中定义:引入tx命名空间
xmlns:tx="http://www.springframework.org/schema/tx"
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
添加声明式事务处理 (如果出现错误,回滚,否则正常提交)
声明事务管理器(切面的类中定义事务控制)
<bean id="trManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
tx:advice
:定义通知(通知关联事物,通知的方法放置到切面的类中)
id
:表示通知方法在容器的唯一标识
transaction-manager
:事物管理,指定对那个事物进行管理
tr:method
:对切入点方法的细化
name
:切入点中的方法名称,比如本例中的save
方法
isolation
:隔离级别
propagation
:传播行为
read-only="false"
:可写数据库,比如增删改,true
表示只 读,比如查询操作。
<tx:advice id="trAdvice" transaction-manager="trManager">
<tx:attributes>
<tx:method name="save*" isolation="DEFAULT" propagation="REQUIRED" read-only="false"/>
<tx:method name="delete*" isolation="DEFAULT" propagation="REQUIRED" read-only="false"/>
<tx:method name="update*" isolation="DEFAULT" propagation="REQUIRED" read-only="false"/>
<tx:method name="*" isolation="DEFAULT" propagation="REQUIRED" read-only="true"/>
</tx:attributes>
</tx:advice>
定义切入点,使切入点关联通知
aop:pointcut
定义切入点,业务层的方法就是切入点
expression
切入点表达式
execution(* com.itheima.service..*.*(..))
表示返回类型任意,com.itheima.service
包及其子包中所有类,类中的所 有方法,参数任意
aop:advisor
配置切入点要关联通知(事物控制层的方法)
<aop:config>
<aop:pointcut id="trPointcut" expression="execution(* com.itheima.service..*.*(..))" />
<aop:advisor advice-ref="trAdvice" pointcut-ref="trPointcut"/>
</aop:config>
这样再测试案例方法,发现事物可以被控制,即如果出现错误,回滚;没有出现错误,提交
Spring事务控制的原理
在控制层访问业务的方法时,实质上产生一个代理对象$Proxy,在代理对象中对业务层的方法用事务进行控制,这样就将业务层的方法用一个事务做了控制
实质上代理产生的代码(因为在容器中进行配置)进行事务控制:这个配置再次验证spring关于aop的思想,即在业务层的方法之前,创建一个代理类,由代理类控制事务的提交和回滚。
如:
try{
accountService.saveAccount(account);
tr.commit();
}
catch(){
tr.rollback();
}
finally{
con.close();
}
异常处理
如果在Dao中或者在Service抛出异常,需要抛出运行时异常。
这里:任何 RuntimeException 将触发事务回滚,但是任何 checked Exception 将不触发事务回滚
在开发过程中,要想对事务进行控制,如果需要抛出异常的时候,需要在Service层或者Dao层的方法中抛出运行时异常,这样才能进行事务控制。例如:
public void update(Customer customer) throws DaoException {
try {
String sql = "update customer set aaamoney=? where id=?";//这里放置aaamoney错误,测试事物的回滚
Object [] args = {customer.getMoney(),customer.getId()};
this.jdbcTemplate.update(sql,args);
} catch (DataAccessException e) {
e.printStackTrace();
//throw new RuntimeException("抛出运行时异常");
throw new DaoException("抛出DAO异常");
}
}
自定义异常(也可以进行事物控制)
如果不想抛出运行时异常,需要指定自定义异常
自定义两个异常类
public class DaoException extends Exception {
public DaoException() {
super();
}
public DaoException(String message) {
super(message);
}
}
public class ServiceException extends Exception {
public ServiceException() {
super();
}
public ServiceException(String message) {
super(message);
}
}
在Spring容器中定义(beans.xml
)
<tx:attributes>
<tx:method name="save*" isolation="DEFAULT" propagation="REQUIRED" read-only="false" rollback-for="com.itheima.exception.DaoException,com.itheima.exception.ServiceException"/>
</tx:attributes>
Spring整合struts2
导入struts2-spring-plugin-2.x.x.jar
包(用于struts2集成spring的插件)
首先在web.xml
中添加Spring的监听器:在web容器容器启动的时候,同时加载Spring容器
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:beans.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
在Spring容器beans.xml
中创建action
对象,由于struts2
是多线程,多实例,需要加上scope="prototype"
<bean id="personAction" class="com.itheima.web.action.PersonAction" scope="prototype">
<property name="personService" ref="personService"></property>
</bean>
还需要stuts.xml
中把
class="com.itheima.web.action.PersonAction
改成bean
中的id
personAction
<action name="PersonAction_*" class="personAction" method="{1}">
<result name="success">/success.jsp</result>
</action>
这样在action
中就可以通过set
方法完成注入。
public void setPersonService(IPersonService personService) {
this.personService = personService;
}
private IPersonService personService;
Spring整合Hibernate
在Spring容器中配置hibernate.cfg,xml
中的配置,以代替之。
在src
下创建一个jdbc.properties
文件
jdbc.driver = com.mysql.jdbc.Driver
jdbc.url = jdbc:mysql:///数据库?useUnicode=true&characterEncoding=utf8
jdbc.user = username
jdbc.password =password
在bean.xml
中添加如下配置
<!-- 加载数据文件 -->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!-- 配置c3p0数据源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
<property name="driverClass" value="${jdbc.driver}" />
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="user" value="${jdbc.user}" />
<property name="password" value="${jdbc.password}" />
<!--连接池中保留的最小连接数。-->
<property name="minPoolSize" value="5" />
<!--连接池中保留的最大连接数。Default: 15 -->
<property name="maxPoolSize" value="30" />
<!--初始化时获取的连接数,取值应在minPoolSize与maxPoolSize之间。Default: 3 -->
<property name="initialPoolSize" value="10"/>
<!--最大空闲时间,60秒内未使用则连接被丢弃。若为0则永不丢弃。Default: 0 -->
<property name="maxIdleTime" value="60"/>
<!--当连接池中的连接耗尽的时候c3p0一次同时获取的连接数。Default: 3 -->
<property name="acquireIncrement" value="5" />
<!--JDBC的标准参数,用以控制数据源内加载的PreparedStatements数量。但由于预缓存的statements
属于单个connection而不是整个连接池。所以设置这个参数需要考虑到多方面的因素。
如果maxStatements与maxStatementsPerConnection均为0,则缓存被关闭。Default: 0-->
<property name="maxStatements" value="0" />
<!--每60秒检查所有连接池中的空闲连接。Default: 0 -->
<property name="idleConnectionTestPeriod" value="60" />
<!--定义在从数据库获取新连接失败后重复尝试的次数。Default: 30 -->
<property name="acquireRetryAttempts" value="30" />
<!--获取连接失败将会引起所有等待连接池来获取连接的线程抛出异常。但是数据源仍有效
保留,并在下次调用getConnection()的时候继续尝试获取连接。如果设为true,那么在尝试
获取连接失败后该数据源将申明已断开并永久关闭。Default: false-->
<property name="breakAfterAcquireFailure" value="true" />
</bean>
创建SessionFactory
,这是spring整合hibernate的核心
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource"></property>
<!-- 加载hibernate.cfg.xml
<property name="configLocation">
<value>classpath:hibernate.cfg.xml</value>
</property>
-->
<!-- 负责加载hibernate的属性文件 -->
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</prop>
<prop key="hibernate.hbm2ddl.auto">update</prop>
<prop key="hibernate.show_sql">true</prop>
</props>
</property>
<!-- 添加映射 -->
<property name="mappingDirectoryLocations">
<array>
<value>classpath:com/itheima/domain</value>
</array>
</property>
</bean>
注解方式
略