文章目录:
2.5.1 商品实体类Goods对应的dao接口和mapper文件
2.5.2 销售记录实体类Sale对应的dao接口和mapper文件
2.8.1 Spring使用注解(@Transactional)控制事务
2.9.1 引入外部属性配置文件(jdbc.properties)
2.9.2 声明数据源、SqlSessionFactory对象、读取mapper文件
2.5 编写dao接口和对应的mapper映射文件
2.5.1 商品实体类Goods对应的dao接口和mapper文件
package com.bjpowernode.dao; import com.bjpowernode.entity.Goods; /** * */ public interface GoodsDao { //查询某个id的商品信息,返回的是Goods对象(商品类) Goods selectById(Integer id); //参数goods表示本次购买的商品id和amount购买数量 int updateGoods(Goods goods); }
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.bjpowernode.dao.GoodsDao"> <!-- 使用insert、update、delete、select标签编写sql语句 --> <select id="selectById" resultType="com.bjpowernode.entity.Goods"> select id,name,amount,price from goods where id = #{id} </select> <update id="updateGoods"> update goods set amount = amount - #{amount} where id = #{id} </update> </mapper>
2.5.2 销售记录实体类Sale对应的dao接口和mapper文件
package com.bjpowernode.dao; import com.bjpowernode.entity.Sale; /** * */ public interface SaleDao { int insertSale(Sale sale); }
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.bjpowernode.dao.SaleDao"> <!-- 使用insert、update、delete、select标签编写sql语句 --> <insert id="insertSale"> insert into sale(gid,num) values(#{gid},#{num}) </insert> </mapper>
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <!-- 设置日志 --> <settings> <setting name="logImpl" value="STDOUT_LOGGING"/> </settings> <mappers> <!-- <mapper resource=""/>--> <package name="com.bjpowernode.dao"/> </mappers> </configuration>
这个异常类主要用来处理购买商品时,库存、商品是否存在这些情况下,所发生的异常信息。
package com.bjpowernode.excetion; /** * 运行时异常 */ public class NotEnoughException extends RuntimeException { public NotEnoughException() { super(); } public NotEnoughException(String message) { super(message); } }
package com.bjpowernode.service; /** * */ public interface BuyGoodsService { /** * @param goodsId: 购买的商品id * @param num: 购买的商品数量 */ void buy(Integer goodsId,Integer num); }
2.8.1 Spring使用注解(@Transactional)控制事务
@Transactional的所有可选属性如下所示:
1. propagation:用于设置事务传播属性。该属性类型为 Propagation 枚举,默认值为 Propagation.REQUIRED。
2. isolation:用于设置事务的隔离级别。该属性类型为 Isolation 枚举,默认值为 Isolation.DEFAULT。
3. readOnly:用于设置该方法对数据库的操作是否是只读的。该属性为 boolean,默认值为 false。
4. timeout:用于设置本操作与数据库连接的超时时限。单位为秒,类型为 int,默认值为-1,即没有时限。
5. rollbackFor:指定需要回滚的异常类。类型为 Class[],默认值为空数组。当然,若只有一个异常类时,可以不使用数组。
6. rollbackForClassName:指定需要回滚的异常类类名。类型为 String[],默认值为空数组。当然,若只有一个异常类时,可以不使用数组。
7. noRollbackFor:指定不需要回滚的异常类。类型为 Class[],默认值为空数组。当然,若只有一个异常类时,可以不使用数组。
8.noRollbackForClassName:指定不需要回滚的异常类类名。类型为 String[],默认值为空数组。当然,若只有一个异常类时,可以不使用数组。
需要注意的是,@Transactional 若用在方法上,只能用于 public 方法上。对于其他非 public 方法,如果加上了注解@Transactional,虽然 Spring 不会报错,但不会将指定事务织入到该方法中。因为 Spring 会忽略掉所有非public 方法上的@Transaction 注解。若@Transaction 注解在类上,则表示该类上所有的方法均将在执行时织入事务。
package com.bjpowernode.service.impl; import com.bjpowernode.dao.GoodsDao; import com.bjpowernode.dao.SaleDao; import com.bjpowernode.entity.Goods; import com.bjpowernode.entity.Sale; import com.bjpowernode.excetion.NotEnoughException; import com.bjpowernode.service.BuyGoodsService; import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; /** * */ public class BuyGoodsServiceImpl implements BuyGoodsService { private SaleDao saleDao; private GoodsDao goodsDao; public void setSaleDao(SaleDao saleDao) { this.saleDao = saleDao; } public void setGoodsDao(GoodsDao goodsDao) { this.goodsDao = goodsDao; } /** * @Transactional 注解放在public业务方法的上面,表示方法有事务功能 * propagation = Propagation.REQUIRED: 设置事务的传播属性,默认值 * isolation = Isolation.DEFAULT: 设置事物的隔离级别,默认值 * readOnly = false: 对数据库的操作不是只读的 * timeout = 20: 超时时限设置为20s * rollbackFor = {NullPointerException.class,NotEnoughException.class}): //两个异常发生任何一个,则执行回滚操作 * 框架首先检查方法抛出的异常是不是在rollback的数组中,如果在,一定执行回滚。 * 如果不在,框架会继续检查抛出的异常是不是RuntimeException,如果是,仍然执行回滚。 */ @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT, readOnly = false, timeout = 20, rollbackFor = {NullPointerException.class,NotEnoughException.class}) @Override public void buy(Integer goodsId, Integer num) { System.out.println("=====buy方法的开始======"); //生成销售记录 Sale sale=new Sale(); sale.setGid(goodsId); sale.setNum(num); saleDao.insertSale(sale); //查询商品 Goods goods=goodsDao.selectById(goodsId); if (goods == null) { throw new NullPointerException(goodsId + "商品不存在。"); }else if (goods.getAmount() < num) { throw new NotEnoughException(goodsId + "商品库存不足。"); } //更新库存 Goods buyGoods=new Goods(); buyGoods.setId(goodsId); buyGoods.setAmount(num); goodsDao.updateGoods(buyGoods); System.out.println("=====buy方法的完成======"); } }
2.9.1 引入外部属性配置文件(jdbc.properties)
jdbc.url=jdbc:mysql://localhost:3306/ssm?useUnicode=true&characterEncoding=utf-8 jdbc.username=root jdbc.password=12345678
<context:property-placeholder location="classpath:jdbc.properties" />
2.9.2 声明数据源、SqlSessionFactory对象、读取mapper文件
<bean id="myDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> <property name="url" value="${jdbc.url}" /> <property name="username" value="${jdbc.username}" /> <property name="password" value="${jdbc.password}" /> </bean> <bean id="factory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="myDataSource" /> <property name="configLocation" value="classpath:mybatis.xml" /> </bean> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="sqlSessionFactoryBeanName" value="factory" /> <property name="basePackage" value="com.bjpowernode.dao" /> </bean>
<bean id="buyGoodsService" class="com.bjpowernode.service.impl.BuyGoodsServiceImpl"> <property name="goodsDao" ref="goodsDao" /> <property name="saleDao" ref="saleDao" /> </bean>
<!-- 声明事务的控制 --> <!-- 声明事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!-- 指定数据源DataSource --> <property name="dataSource" ref="myDataSource" /> </bean> <!-- 开启事务注解驱动,告诉框架使用注解管理事务 transaction-manager: 指定事务管理器的id --> <tx:annotation-driven transaction-manager="transactionManager" />
service.buy(1001,10);表示我们此时要购买1001号商品10台。
@Test public void test01() { ApplicationContext ctx=new ClassPathXmlApplicationContext("applicationContext.xml"); BuyGoodsService service= (BuyGoodsService) ctx.getBean("buyGoodsService"); service.buy(1001,10); }
这里的 id 等于5,是因为在这之前,我做了几次代码测试,sale表中的主键(销售记录编号)是自增的,所以这里是5。
后面的 gid=1001,num=10,表示编号为1001的商品,卖出了10台。
service.buy(1002,200);表示我们此时要购买1002号商品200台。(而数据库中1002号商品总库存只有50台,所以这里会发生异常)
@Test public void test02() { ApplicationContext ctx=new ClassPathXmlApplicationContext("applicationContext.xml"); BuyGoodsService service= (BuyGoodsService) ctx.getBean("buyGoodsService"); service.buy(1002,200); }
这里产生的异常是我们自定义的NotEnoughException 异常。所以事务一定执行回滚。
service.buy(1005,20);表示我们此时要购买1005号商品20台。(而数据库中并不存在1005号商品,所以这里会发生异常)
@Test public void test03() { ApplicationContext ctx=new ClassPathXmlApplicationContext("applicationContext.xml"); BuyGoodsService service= (BuyGoodsService) ctx.getBean("buyGoodsService"); service.buy(1005,20); }
这里产生的异常是Java中Exception类下的NullPointerException 异常。所以事务一定执行回滚。
4.使用AspectJ框架控制事务
使用这种方法控制事务的步骤如下:
1. 在pom.xml中加入 spring-aspects 依赖
2. 在spring配置文件中声明事务的内容:①声明事务管理器;②声明业务方法需要的事务属性;③声明切入点表达式。
与上面使用 注解(@Transactional)相比,大部分代码都是一样的。只有Spring的配置文件中有所改动。
将第一种方法中spring配置文件中的声明事务注解驱动删掉,换成下面的代码就可以了。
<!-- 声明业务方法的事务属性(隔离级别、传播行为、超时时限) id: 给业务方法配置事务段代码起个名称,唯一值 transaction-manager: 事务管理器的id --> <tx:advice id="serviceAdvice" transaction-manager="transactionManager"> <!-- 给具体的业务方法增加事务的说明 --> <tx:attributes> <!-- name的值: 1)业务方法的名称 2)带有通配符* --> <tx:method name="buy" propagation="REQUIRED" isolation="DEFAULT" timeout="20" read-only="false" rollback-for="java.lang.NullPointerException,com.bjpowernode.excetion.NotEnoughException"/> </tx:attributes> </tx:advice> <!-- 声明切入点表达式,表示哪些包中的类、方法需要添加事务 --> <aop:config> <!-- id: 切入点表达式的名称,唯一值 expression: 切入点表达式 --> <aop:pointcut id="servicePointcut" expression="execution(* *..service..*.*(..))" /> <aop:advisor advice-ref="serviceAdvice" pointcut-ref="servicePointcut" /> </aop:config>
5.两种方式各自的使用特点
一、使用@Transactional注解
1. Spring框架自己提供的事务控制。
2. 适合中小型项目。
3. 使用方便,效率高。
二、使用AspectJ框架
1. 缺点:理解难,配置复杂。
2. 优点:代码和事务配置是分开的。控制事务,其源代码不用修改。能快速的了解和掌控项目的全部事务,适合大型项目。