Spring
内容管理
- Spring事务
- Spring事务管理
- 事务实例模拟
- 给业务方法增加事务
- 注解@Transactional
- 使用AspectJ的aop配置管理事务
- Spring web
- 监听器以及工具类
- ContextLoadListener
- WebApplicationContextUtils
- Tomcat启动错误: java.lang.NoClassDefFoundError: org/springframework/context/ApplicationContext
Javaweb —Spring
Spring收尾—事务、web
Spring集成mybatis最关键的就是3个对象,Datasource对象、SqlSessionFactory对象、利用MapperScannerConfig创建的dao对象 — 都是在配置文件中完成,德鲁伊druid不需要driver的信息
Spring事务
- 事务是什么?
事务其实就是完成业务逻辑的一组sql语句的集合【一般是DML】,集合中有多条sql语句,包括UPDATE、INSERT、DELETE等,事务的特点就是ACID, atomic(原子性)、consistency(一致性) 同时成功或者同时失败、isolation(隔离性)、durability(持久性),作为一个整体执行。
- 什么时候使用事务?
当操作涉及多个表,或者而多个DML语句,这些语句对修改表中的数据,为了确保数据安全,需要使用事务,确保同时成功完成功能,或者同时失败保证安全【转账,不能增加或者减少了】
- 在程序中,事务应该在什么位置处理?
事务本来在Dao层,但一般情况下,需要将事务上升到业务层,这样做是为了使用事务的特性来管理具体的事物。因为service层一般会调用多个dao方法,执行多个sql语句,这个时候,为了确保数据的安全,就必须要使用事务
- 在JDBC或者Mybatis如何使用事务?
在控制台单独写事务,开启事务,rollback或者commit;并且还有是事务的4个隔离级别,mysql默认repeatable;JDBC访问数据库,使用的是Connection对象来执行的事务,最开始setAutoCommit为false;然后最后commit;在catch中rollback; 而在mybatis中使用的是sqlSession对象来处理的事务,比如rollback; 之前的整合过程是因为是自动提交事务,所以没有凸显事务
不同的数据库访问技术,处理事务的对象、方法是不同的,比如JDBC和Mybatis的方法,并且访问数据库访问技术使用的事务的原理,掌握多种数据库中处理逻辑,什么时候提交事务、回滚事务,处理方法。
多种数据库的访问技术,有不同的机制处理的机制和对象,Spring提供了处理事务的统一模型【将事务处理规范化】,使用统一的步骤,完成多种不同数据库访问技术的事务处理、比如mybatis、hibernate等访问数据库的技术 ------> spring事务处理模型: 定义了事务处理的各个方面,定义了最基本的处理步骤【规范化】就像AOP一样声明式事务:把事务相关的资源和内容都提供给spring、spring就能处理事务的提交回滚了,几乎不需要写代码
在Spring框架中,有两种实现事物的方式:
- 使用Spring的事务注解管理事务
- 使用AspectJ的AOP配置管理事务
Spring事务管理
Spring事务管理,主要依赖的两个重要的接口
PaltformTransactionManager
上面已经提到了只要将事务的资源交给spring就可以了,那么事务的提交、回滚是依靠的就是事务管理器对象
,代替完成rollback和commit; 事务管理器是一个接口和众多实现类:也就是PaltformTransactionManager接口和其实现类:Spring把每一种数据库访问技术对应的事务处理类都创建了的
如果使用的mybatis访问数据库------>spring创建好的是DataSourceTransactionManager
如果使用的是hibernate访问数据库---->spring船舰号的是HibernateTransactionManager ……
虽然不需要自己创建类,但是需要告诉Spring使用的是哪一种技术,这里只需要在配置文件中使用bean标签声明事务管理器实现类,如果使用mybatis,那么
<bean id="xxx" class="……DataSourceTransactionManager"/>
TransactionDefinition
除了告诉spring使用什么,还需要告诉事务的类型。TransactionDefinition接口中定义了事务描述相关的常量:其中包括事务的隔离级别、传播行为、默认的延时时限、操作等,说明的事务的信息包括
-
事务的隔离级别 : spring框架使用的是几个整数常量表示的,ISOLATION开头,mysql默认是REPEATABLE_READ;oracle为READ_COMMITED
- ISOLATION_READ_UNCOMMITED: 读未提交,未解决任何的并发问题
- ISOLATION_READ_COMMITED: 读已提交,存在不可重复度和幻读
- ISOLATION_REAPTABLE_READ: 可重复读、解决脏读,不可重复度,但是存在幻读
- ISOLATION_SERLAIZABLE: 串行化,不存在并发的问题
-
事务的超时时限: 表示一个方法的最长的执行时间。如果方法执行时间超过了时间,事务就会回滚,单位是秒,一般不设置,使用默认的TIMOUT_DEFAULT: -1
-
事务的传播行为: 控制业务方法是不是有事务的,是什么样的事务(7个)处于不同事务中的方法在相互调用的时候【方法栈】,执行期间业务的维护情况。比如A事务的方法doSome调用B事务的方法doOther(),调用执行期间的事务的维护情况,称为事务的传播行为,事务的传播行为加在方法上 。 传播行为都是以PROPAGATION开头的
- PROPAGATION_REQUIRED: required,指定的方法必须在事务内执行,当前方法存在事务,必须加入当前的事务,没有则创建新事务。是Spring的默认的传播行为(被调用者需要事务)
- PROPAGATION_SUPPORTED:supported:方法支持当前事务,(被调用者支持当前事务),如果没有事务,也可以 ---- 【查询操作,有没有事务都行】
- PROPAGETION_REQUIRED_NEW:总是会新建一个事务,原来存在事务就挂起,直到当前事务执行完毕
还有几个,就在下面的图片中,因为使用频率不高
除了告诉Spring访问的方式,还有事务的相关信息,还要知道事务的提交回滚的时机
当业务方法执行成功,没有异常抛出,当方法执行完毕的时候,spring会提交commit事务
当业务方法出现 运行时异常,spring执行回滚,调用事务管理器的rollback
当业务方法出现 非运行时异常,【需要处理】,主要时受查异常,提交事务commit
这里再另外的笔试题中提到过,异常分为RuntimeException和非运行时异常,运行时异常不能编译检查出,不需要事先处理,比如NullPointerException; 非运行时异常,也就时受查异常和Error,比如IOException、SQLException
Spring的事务是统一的模型。1)也就是管理事务的时接口PaltformTransactionManager事务管理器,使用bean指定访问的方式。 2)指定哪些类、哪些方法需要加入事务,3)同时通过TransactionDefinition指定事务的信息包括隔离级别、传播行为、超时时限等
事务实例模拟
这个实例—>购买商品、模拟用户下单,想订单表添加销售记录,从商品表减少库存【其实就是模拟的一个转账的行为】
首先创建数据库表,创建两张表,销售记录表sale、商品库存表goods
CREATE TABLE sale(
id INT PRIMARY KEY AUTO_INCREMENT,
gid INT NOT NULL,
nums INT,
FOREIGN KEY(gid) REFERENCES goods(id)
)
CREATE TABLE goods(
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(80),
amount INT,
price DECIMAL
)
注意,因为计算机存储浮点数是不精确的,所以叫做浮点数,所以这里的商品价格采用定点数DECIMAL
然后简单插入数据【这里是生成的,没有手写】
NSERT INTO `cfengbase`.`goods` (`id`, `name`, `amount`, `price`) VALUES ('1001', '笔记本', '70', '15.2');
INSERT INTO `cfengbase`.`goods` (`id`, `name`, `amount`, `price`) VALUES ('1002', '肥皂', '80', '7.88');
之后还是使用spring集成mybatis来创建这个实体类,这里使用前面的pom,不再导入依赖,编写是从数据持久层到业务层开始写。这里就先编写实体类,这里两张表,创建两个实体类。 注意重写toString方法,如果不重写,println会默认调用父类Object的toString方法,就是 ……@……的格式,看不到具体的值
package cfeng.domain;
public class Goods {
private int id;
private String name;
private int amount;
private float price;
public Goods() {
}
public Goods(int id, String name, int amount, float price) {
this.id = id;
this.name = name;
this.amount = amount;
this.price = price;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAmount() {
return amount;
}
public void setAmount(int amount) {
this.amount = amount;
}
public float getPrice() {
return price;
}
public void setPrice(float price) {
this.price = price;
}
@Override
public String toString() {
return "Goods{" +
"id=" + id +
", name='" + name + ''' +
", amount=" + amount +
", price=" + price +
'}';
}
}
另外一个创建是相同的,接下来开始写dao层的部分,就是接口和对应的mapper文件
package cfeng.dao;
import cfeng.domain.Sale;
public interface SaleDao {
//插入销售记录
public int insertSale(Sale sale);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org/DTD Mapper 3.0" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cfeng.dao.SaleDao">
<insert id="insertSale">
INSERT INTO sale (gid,nums) VALUES (#{gid},#{nums})
</insert>
</mapper>
另外一个接口的定义方式相同
package cfeng.dao;
import cfeng.domain.Goods;
public interface GoodsDao {
//更新库存,goods表示本次购买的商品
public int updateGoods(Goods goods);
//查询商品的信息、主键
public Goods selectGoods(int id);
}
<?xml version="1.0" encoding="UTF-8" ?>
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org/DTD Mapper 3.0" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cfeng.dao.GoodsDao">
<select id="selectGoods" resultType="cfeng.domain.Goods">
SELECT id,name,price,amount FROM goods WHERE id = #{id}
</select>
<update id="updateGoods"> <!-- 需要注意不要写错名称,要对应,还有set的表名不要掉了-->
UPDATE goods SET amount = amount - #{amount} WHERE id = #{id}
</update>
</mapper>
mybatis的主配置文件,没有什么可以设置的了,现在就一个别名和一个mapper设置,环境都整合到datasource中了
<?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>
<!-- 这里也是从类路径的根开始,因为resource直接复制到classes下面,这里就直接写名称即可-->
<properties resource="db.properties"/>
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
<typeAliases>
<package name="cfeng.domain"/>
</typeAliases>
<!-- 以后的环境都是用druid了 -->
<mappers>
<!-- <mapper resource="cfengdaoStudentDao.xml"/>-->
<package name="cfeng/dao"/>
</mappers>
</configuration>
这里为了演示事务遇到运行时异常会自动rollback,这里创建一个类NotEnoughException 继承RuntimeException类,重写方法
package cfeng.exception;
public class NotEnoughException extends RuntimeException {
public NotEnoughException() {
super();
}
public NotEnoughException(String message) {
super(message);
}
}
之后就是简单编写service类来进行操作
package cfeng.service;
public interface PurchaseGoods {
//定义购买商品的方法,参数为购买商品的id,和购买的数量
public void purchase(int goodsId, int amount);
}
package cfeng.service.impl;
import cfeng.dao.GoodsDao;
import cfeng.dao.SaleDao;
import cfeng.domain.Goods;
import cfeng.domain.Sale;
import cfeng.exception.NotEnoughException;
import cfeng.service.PurchaseGoods;
public class PurchaseGoodsImpl implements PurchaseGoods {
private SaleDao saleDao;
private GoodsDao goodsDao;//建立set方法方便使用配置文件的set注入
@Override
public void purchase(int goodsId, int amount) {
//记录销售的记录,依赖saledao
//实体类的对象关联表,不用spring容器管理
System.out.println("业务方法开始执行");
Sale saleRecord = new Sale();
saleRecord.setGid(goodsId);
saleRecord.setNums(amount);
saleDao.insertSale(saleRecord);
//更新库岑,依赖goodsdao ---->在这里抛出异常可以更方便看到rollback
Goods goods = goodsDao.selectGoods(goodsId);
if(goods == null) {
throw new NullPointerException("商品编号为" + goodsId +"的商品不存在");
}else if(goods.getAmount() < amount) {
throw new NotEnoughException("商品编号为" + goodsId +"的商品库存不足");
}
Goods purchasegoods = new Goods();
purchasegoods.setId(goodsId);
purchasegoods.setAmount(amount);
goodsDao.updateGoods(purchasegoods);
System.out.println("业务方法成功执行");
}
public void setSaleDao(SaleDao saleDao) {
this.saleDao = saleDao;
}
public void setGoodsDao(GoodsDao goodsDao) {
this.goodsDao = goodsDao;
}
}
//为了方便查看,在方法开始和结束时进行提示输出
这里为了方便查看发生运行时异常的回滚,所以将异常在两次sql操作之间抛出
接下来写一个测试方法即可;使用TRUNCATE就可以避免删除后自增不连续
@Test
public void testMybatis() {
//要读取spring的属性配置文件进行容器的创建
ApplicationContext springContainer = new ClassPathXmlApplicationContext("applicationContext.xml");
PurchaseGoods purchase = (PurchaseGoods) springContainer.getBean("studentService");
// Arrays.stream(springContainer.getBeanDefinitionNames()).forEach(System.out::println);
purchase.purchase(1001,1);
}
简单测试
业务方法开始执行
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7adf16aa] was not registered for synchronization because synchronization is not active
JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@768ccdc5] will not be managed by Spring
==> Preparing: INSERT INTO sale (gid,nums) VALUES (?,?)
==> Parameters: 1001(Integer), 1(Integer)
<== Updates: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7adf16aa]
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@303e3593] was not registered for synchronization because synchronization is not active
JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@768ccdc5] will not be managed by Spring
==> Preparing: SELECT id,name,price,amount FROM goods WHERE id = ?
==> Parameters: 1001(Integer)
<== Columns: id, name, price, amount
<== Row: 1001, 笔记本, 13, 30
<== Total: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@303e3593]
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@3b9d6699] was not registered for synchronization because synchronization is not active
JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@768ccdc5] will not be managed by Spring
==> Preparing: UPDATE goods SET amount = amount - ? WHERE id = ?
==> Parameters: 1(Integer), 1001(Integer)
<== Updates: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@3b9d6699]
业务方法成功执行
这里故意开启日志,可以清除看到sqlsession对象时开启和关闭
删除数据后,测试超出库存的情况
业务方法开始执行
cfeng.exception.NotEnoughException: 商品编号为1001的商品库存不足
查看数据库表
mysql> SELECT * FROM sale;
+----+------+------+
| id | gid | nums |
+----+------+------+
| 1 | 1001 | 1000 |
+----+------+------+
1 row in set (0.00 sec
但是goods表中的商品数量没有减少 ----> 不开启事务导致业务混乱
给业务方法增加事务
注解@Transactional
spring提供事务的处理方式,第一种的通过 AOP机制注解的方式增加事务,使用 注解@Transactional
增加事务,这个注解时框架自己提供的,放在public方法的上面,表示当前方法具有事务,可以给注解的属性赋值,表示具体的隔离级别
通过注解方式,可以将事务放到对应的public方法中,事先事务管理,其所有的可选属性如下
- propagation:用于设置事务的传播的属性,属性为Propogation的枚举,默认是PROPAGATION_REQUIRED
- isolation : 用于设置事务的隔离几倍,也是isolation的枚举,默认值为isolation.DEFAULT
- readOnly: 用于设置该方法对数据库的操作是否为只读的,属性为boolean类型,默认是false 【当数据库的查询操作可以设置为true;可以提高效率 — 只读会安全一点,但是当然就慢一点】
- timeout: 设置本操作与数据库连接的超时时限,单位为秒,int类型,默认是-1
- rollbackFor:指定需要回滚的异常类,类型为Class[],默认是空数组,只有有一个异常类,可以不用数组
- rollbackForClassName:指定需要回滚的异常类的类名,类型为String,默认是空
使用注解@Transactional的步骤
- 声明事务管理其对象,< bean>标签使用dataSourceTransactionManager—mybatis的
<!-- 声明事务管理器对象 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 要说明连接的数据库的信息,知道为哪张表开启事务 -->
<property name="dataSource" ref="myDataSource"/>
</bean>
- 开启事务注解驱动,告诉spring框架,要使用注解的方式来管理事务【spring使用aop机制,创建@Transactional所在的类的代理对象,给方法加入事务的功能】---- 在业务方法的执行之前开启事务,在执行之后回滚或者提交事务,相当于是使用AOP中的@Around注解对切面类进行注解
//模拟一下,就类似之前使用JDBC管理事务
@Around(……)
public void ……(){
//开启事务
try{
执行业务方法;
提交事务;commit
}catch(Exception e){
……
回滚事务 rollback;
}
}
--------------------文件中的配置------------------- 注意:事务transaction简写为tx,所以要加入spring-tx依赖【jdbc是基础,所以也要加入】---
<!-- 开启事务注解驱动,告诉spring使用注解的方式管理事务【扫描】,创建代理对象 annotation-driven 属性就是事务管理器对象,传入id
注意一定要选择正确的命名空间的,这里有多个,要选择tx【transaction简写】空间的,可以看到和切面类的编写相同,出现了通知符号
-->
<tx:annotation-driven transaction-manager="transactionManager"/>
- 在方法上加入注解@Transactional
@Transactional(
propagation = Propagation.REQUIRED,
isolation = Isolation.DEFAULT,
readOnly = false,
rollbackFor = {NullPointerException.class,NotEnoughException.class}
)
@Override
public void purchase(int goodsId, int amount) {
这是使用的AOP的方式进行的事务的处理
//执行测试类的方法
System.out.println(purchase);
purchase.purchase(1001,1000);
cfeng.service.impl.PurchaseGoodsImpl@72f46e16
业务方法开始执行
cfeng.exception.NotEnoughException: 商品编号为1001的商品库存不足
查询数据库表
Empty set (0.00 sec) -----> 说明进行了事务回滚,可以开日志看看
业务方法开始执行
Creating a new SqlSession
Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@182f1e9a]
JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@64f857e7] will be managed by Spring
出现异常
Releasing transactional SqlSession ----- 释放回滚了事务
Transaction synchronization deregistering SqlSession
Transaction synchronization closing SqlSession
可以从日志中发现开启了事务【而上面不加注解的时候默认的是because synchronization is not active没有激活事务【还是之前的自动提交机制】
业务方法成功执行
Transaction synchronization committing SqlSession ----- 提交了事务
Transaction synchronization deregistering SqlSession
Transaction synchronization closing SqlSession
将数量改到正常值就可以发现执行成功,并且提交了事务
回滚就是清除之前的操作,但是对于AUTO_INCREMENT来说这个是可以看到操作痕迹的,已经用过的是不能再用了
mysql> SELECT * FROM sale;
+----+------+------+
| id | gid | nums |
+----+------+------+
| 4 | 1001 | 1 |
| 6 | 1001 | 3 |
| 8 | 1001 | 3 |
+----+------+------+
8次操作一共有5次都是失败回滚的
其实上面的注解不需要写那么多,因为基本都是默认值,直接@Transactional
@Transactional
@Override
public void purchase(int goodsId, int amount) {
这样也是正常执行的
这里关于rollbackFor解释一下
spring框架会检测方法抛出的异常是不是在rollbackFor属性值数组中,rollbackFor列表中所有的异常,一旦抛出,都会回滚,所以将非运行时异常加到列表中,也会混滚
如果抛出的异常不在属性中,那么spring会检查这个异常的类型,如果是运行时异常,就回滚,不是就不会rollback
使用AspectJ的aop配置管理事务
使用xml配置事务的代理方式的不足就是每一个目标类就要配置事务代理,当目标类过多的时候,配置会很繁琐;有很多的类和方法需要配置事务,在spring配置文件中声明类,这种方式业务方法和事务代码完全分离;【都是在配置文件中实现】
实现首先就要有spring-aspects依赖
- 声明事务管理器,还是配置bean,选择的是dataSourceTransactionManager
<!-- 声明事务管理器对象 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 要说明连接的数据库的信息,知道为哪张表开启事务 -->
<property name="dataSource" ref="myDataSource"/>
</bean>
- 声明方法需要的事务的类型,就是事务的属性包括传播行为,隔离级别,时限等 【注解的方式只要一个annotation-driven即可,写入注解中】 声明通知advise
<!-- name的方式,第一种是方法的完整的方法,不带有包和类
还可以使用通配符* 的方式: *代表任意的字符
method代表的是pointcut,切面
其中的其他的事物的属性可以配置,rollbackFor必须使用异常类名的全限定名
-->
<!-- id自定义,表示配置内容-->
<tx:advice id="myadvise" transaction-manager="transactionManager">
<tx:attributes> <!--表示要配置的事务的属性 method是作用的方法,切入点 -->
<tx:method name="purchase" propagation="REQUIRED" isolation="DEFAULT"
rollback-for="java.lang.NullPointerException,cfeng.exception.NotEnoughException"/>
</tx:attributes>
</tx:advice>
在实际项目开发中,可能有很多的业务方法,为了便于使用通配符,一般方法的命名要遵循一定的规则
添加操作 public void addXXX()
修改操作 …… modifyXXX()
删除操作 …… removeXXX
查询操作 …… queryXXX()
这样子,在配置文件中,就可以使用通配符了; 优先级还是从最精确的开始的
<tx:method name="add*" propagation="REQUIRED" isolation="DEFAULT"/>
- 配置aop:指定哪些类需要创建代理
因为上面的tx的advice标签中配置的只是方法的名称,并没有指明包名和类名,所以需要使用aop进行标签
<!-- 配置aop -->
<aop:config>
<!-- 配置切入点表达式,指出哪些类需要应用事务 id 切入点表达式的名称 切入点表达式:指出切入点表达式-->
<aop:pointcut id="servicePt" expression="execution(* *..service..*.*(..))"/>
<!-- 配置增强器: 关联pointcut和上面的advisce -->
<aop:advisor advice-ref="myadvise" pointcut-ref="servicePt"/>
</aop:config>
一定要将通知advice和切入点表达式进行增强
完整的spring的配置文件就是
<?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:util="http://www.springframework.org/schema/util"
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/util https://www.springframework.org/schema/util/spring-util.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 声明数据源Datasource ,作用是连接数据库,不需要之前mybatis的environment;数据源对象自带数据库的信息-->
<!-- 连接阿里巴巴的德鲁伊数据源 -->
<context:property-placeholder location="classpath:db.properties"/>
<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.user}"/>
<property name="password" value="${jdbc.pwd}"/>
<property name="maxActive" value="20"/>
</bean>
<bean id="mySqlSessionFactory" 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="mySqlSession"/>
<property name="basePackage" value="cfeng.dao"/>
</bean>
<bean name="studentService" class="cfeng.service.impl.PurchaseGoodsImpl">
<property name="saleDao" ref="saleDao"/>
<property name="goodsDao" ref="goodsDao"/>
</bean>
<!-- 声明事务管理器对象 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 要说明连接的数据库的信息,知道为哪张表开启事务 -->
<property name="dataSource" ref="myDataSource"/>
</bean>
<!-- id自定义,表示配置内容-->
<tx:advice id="myadvise" transaction-manager="transactionManager">
<tx:attributes> <!--表示要配置的事务的属性 method是作用的方法,切入点 -->
<tx:method name="purchase" propagation="REQUIRED" isolation="DEFAULT"
rollback-for="java.lang.NullPointerException,cfeng.exception.NotEnoughException"/>
</tx:attributes>
</tx:advice>
<!-- 配置aop -->
<aop:config>
<!-- 配置切入点表达式,指出哪些类需要应用事务 id 切入点表达式的名称 切入点表达式:指出切入点表达式-->
<aop:pointcut id="servicePt" expression="execution(* *..service..*.*(..))"/>
<!-- 配置增强器: 关联pointcut和上面的advisce -->
<aop:advisor advice-ref="myadvise" pointcut-ref="servicePt"/>
</aop:config>
</beans>
这样再次执行测试方法,和上面的是相同的
Spring web
在普通的SE项目中,使用spring容器,就简单在main方法中创建容器对象,ApplicationContext springContainer = new ClassPathXmlApplicationContext(“config”);
但是web项目中是在tomcat服务器上运行的,不能再这样配置了,这里就使用之前的Student来进行简单的web的项目的开发
首先创建项目的时候就要配置好模板,选择webapp
这个时候就加入依赖,加入之前的provide的servlet和jsp的依赖即可
//可以看看目前用到的依赖的jar
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<!-- 添加mybatis依赖-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.9</version>
</dependency>
<!--添加mysql依赖 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.27</version>
</dependency>
<!-- 加入pageHelper依赖-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.3.0</version>
</dependency>
<!--加入Spring依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.13</version>
</dependency>
<!-- 加入J2EE的依赖,使用@Resource -->
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>
<!-- 加入AspectJ依赖,方便实现AOP -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.13</version>
</dependency>
<!--spring transaction -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.3.14</version>
</dependency>
<!-- Spring jdbc-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.14</version>
</dependency>
<!-- mybatis 和spring集成-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.6</version>
</dependency>
<!-- 德鲁伊druid连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.8</version>
</dependency>
<!--jsp的依赖,不需要tomcat了 -->
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.1</version>
<scope>provided</scope>
</dependency>
<!--加入servlet依赖,(servlet的jar包) 不需要再配置tomcat了 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<!-- 在服务器使用spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.3.14</version>
</dependency>
</dependencies>
导入依赖,然后简单建立一个action就可以了
package cfeng.controller;
import cfeng.domain.Goods;
import cfeng.service.PurchaseGoods;
import cfeng.util.JsonUtil;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;
@WebServlet("/OAquery")
public class PurchaseAction extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//没有注册的信息
//不需要new对象,service在spring容器中,这里先简单将之前的测试代码直接拿进来
// String config = "springconfig.xml";
// ApplicationContext springcontainer = new ClassPathXmlApplicationContext(config);
ApplicationContext springcontainer = WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext());
PurchaseGoods service = (PurchaseGoods) springcontainer.getBean("purchaseService");
List<Goods> goodsList = service.queryGoods();
StringBuffer json = new StringBuffer("{len:");
json.append(goodsList.size() + "," + "data:[");//之前少了一个data
for(int i = 0; i < goodsList.size(); i++) {
Goods pro = goodsList.get(i);
StringBuffer js = JsonUtil.getJSON(pro);
json.append(js);
if(i <goodsList.size() -1) {
json.append(",");
}
}
json.append("]}");
resp.setContentType("text/html;charset=utf-8");
PrintWriter out = resp.getWriter();
out.print(json);
out.flush();
out.close();
}
}
之后大概看一下html文件
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>cfengOA.助力</title>
<script src="js/JQuery1.8.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript">
$(function () {
$.ajax({
url: "/springWeb/OAquery",
dataType: "text",
success(resp) {
resp = eval("(" + resp + ")");
var list = "<table><tr><th>商品编号</th><th>商品名称</th><th>商品库存</th><th>商品价格</th></tr>"
for (var i = 0; i < resp.len; i++) {
var goods = resp.data[i];
list += "<tr><td>" + goods.id + "</d>";
list += "<td>" + goods.name + "</d>";
list += "<td>" + goods.amount + "</d>";
list += "<td>" + goods.price + "</d></tr>";
}
var goodsdiv = $("#goods")[0];
goodsdiv.innerHTML = list;
}
});
})
</script>
</head>
<body>
<h1 align="center">OA仓储System</h1>
<hr color="pink"/>
<div id="goods"></div>
</body>
</html>
这里使用的时JQuery进行编写,这样之后就可以进行页面的展示了;可以看到,这里并没有通过new的方式创建对象,直接使用的时spring容器;
监听器以及工具类
如果像以前测试类那种new 一个spring容器,这样每次访问的时候都会创建容器,这是不合理的,因为创建容器非常的消耗资源,所以按照以前开通conn池的思路相同,创建一个监听器【全局对象声明周期的监听器】,开启的时候就创建spring对象,放到全局作用域中,当然map的形式就可以了;
需求就是将spring容器只是创建一次,并且容器应该放到全局作用域对象servletContext中
也就是ServletContext.setAttribute(key, springcontainer);
监听器可以自己实现,也可以用spring框架中写好的监听器ContextLoadListener
ContextLoadListener
- 要使用这个监听器,要先加入依赖,就是加入spring-web
<!-- 在服务器使用spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.3.14</version>
</dependency>
- 加入后就可以使用这个监听器了,接着就是在web.xml文件中进行监听器的配置;【tomcat容器】,这里输入contextLoadListener即可;监听器创建后会读取spring的配置文件,因为自动创建spring的容器对象,需要去读取文件创建容器; 但是这里默认时读取WEB-INF/applicationcontext.xml; 路径不正确,所以接下来需要配置路径
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
- 配置路径使用context-param标签即可;选择configLoaction — 表示的就是spring配置文件的位置;路径时在类路径下面;所以加上classpath:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springconfig.xml</param-value>
</context-param>
- 从全局作用域中获取spring容器 【想要获取servletContext这个全局作用域, 可以直接通过getServletContext获取全局作用域,之前分析servlet就分析过的【因为GenericServlet实现了ServletConfig】所以可以this.getServletConfig获取;
//在监听器的内部,这个键值对的键时WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE (WebApplicationContext就是继承的ApplicationContext) 相当于就是ApplicationContext的web专业版
ApplicationContext springContext = null;
Object attr = getServletContext().getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE );
if(attr != null) {
springcontainer = (ApplicationContext)attr;
}
WebApplicationContextUtils
但是这样子有点麻烦,因为这里的key太长了,可以直接使用spring-web包中的WebApplicationUtil功能类
ApplicationContext springcontainer = WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext());
这里直接可以通过这个功能类的方法getRequiredWebApplictionContext获取需要的web版的spring容器;这里需要的参数就是全局作用域对象 this.getServletContext
这个方法的内部
public static WebApplicationContext getRequiredWebApplicationContext(ServletContext sc) throws IllegalStateException {
WebApplicationContext wac = getWebApplicationContext(sc);
if (wac == null) {
throw new IllegalStateException("No WebApplicationContext found: no ContextLoaderListener registered?");
} else {
return wac;
}
}
public static WebApplicationContext getWebApplicationContext(ServletContext sc) {
return getWebApplicationContext(sc, WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
}
可以看到功能类的内部还是使用的时ServletContext---sc;然后还是使用给的就是WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE来取出的spring容器
Tomcat启动错误: java.lang.NoClassDefFoundError: org/springframework/context/ApplicationContext
这个异常一直伴随了我两个小时,各种各样的思考,项目搬动了几次,也没有发现问题;这个异常的意思就是找不到jar包context;但是maven配置的时候配置了的,所以很疑惑,最后发现: 项目结构中没有lib目录
编译的过程就是:
- 编译,IDEA在保存/自动保存后不会做编译,不像Eclipse的保存即编译,因此在运行server前会做一次编译(其实就是maven构建,这就是为啥我们可以不用自己用maven跑一遍的原因)。编译后class文件存放在指定的项目编译输出目录(target)下;
- 根据artifact中的设定对目录结构(如上面的WEB-INF的classes和lib)进行创建;
- 拷贝web资源的根目录下的所有文件到artifact的目录(不仅仅是WEB-INF的classes和lib)下;
- 拷贝编译输出目录下的classes目录到artifact下的WEB-INF(WEB-INF/classes)下;
- 拷贝lib目录下所需的jar包到artifact下的WEB_INF/lib下;
- 运行server,运行成功后,自动打开浏览器访问指定url。
所以很关键的步骤: 首先项目中要有WEB_INF/lib文件夹; 之后进入项目结构中
最开始进入的时候,发现项目中并没有WEB_INF和lib的目录,这是因为:
jar包引用了maven的本地仓库,导致我们的war结构(其实就是WEB-INF/lib目录)发生了变动,lib目录的jar包被清空了
这里就是因为创建的时候就没有创建lib,所以所有的jar包都没有携带上,自然找不到spring-context了; 这个时候解决办法,就是点击右侧的available
右键项目-> 选择PUt into output root
这样就出现了lib目录,重启tomcat就不会报错了
接下来就是MVC了,封装servlet的框架????
最后
以上就是坦率花生最近收集整理的关于框架技术 ---- Spring收尾Spring事务Spring web的全部内容,更多相关框架技术内容请搜索靠谱客的其他文章。
发表评论 取消回复