yangbo i need a girlfriend ^-^

Spring 高级

2016-11-30
yangbo

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>

注解方式


评论