框架技术 ---- Spring收尾

Spring

Javaweb —Spring


Spring收尾—事务、web


Spring集成mybatis最关键的就是3个对象,Datasource对象、SqlSessionFactory对象、利用MapperScannerConfig创建的dao对象 — 都是在配置文件中完成,德鲁伊druid不需要driver的信息

Spring事务

  1. 事务是什么?

事务其实就是完成业务逻辑的一组sql语句的集合【一般是DML】,集合中有多条sql语句,包括UPDATE、INSERT、DELETE等,事务的特点就是ACID, atomic(原子性)、consistency(一致性) 同时成功或者同时失败、isolation(隔离性)、durability(持久性),作为一个整体执行。

  1. 什么时候使用事务?

当操作涉及多个表,或者而多个DML语句,这些语句对修改表中的数据,为了确保数据安全,需要使用事务,确保同时成功完成功能,或者同时失败保证安全【转账,不能增加或者减少了】

  1. 在程序中,事务应该在什么位置处理?

事务本来在Dao层,但一般情况下,需要将事务上升到业务层,这样做是为了使用事务的特性来管理具体的事物。因为service层一般会调用多个dao方法,执行多个sql语句,这个时候,为了确保数据的安全,就必须要使用事务

  1. 在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框架中,有两种实现事物的方式:

  1. 使用Spring的事务注解管理事务
  2. 使用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="cfeng\dao\StudentDao.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的步骤

  1. 声明事务管理其对象,< bean>标签使用dataSourceTransactionManager—mybatis的
 <!-- 声明事务管理器对象 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!-- 要说明连接的数据库的信息,知道为哪张表开启事务 -->
        <property name="dataSource" ref="myDataSource"/>
    </bean>
  1. 开启事务注解驱动,告诉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"/>
  1. 在方法上加入注解@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依赖

  1. 声明事务管理器,还是配置bean,选择的是dataSourceTransactionManager
  <!-- 声明事务管理器对象 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!-- 要说明连接的数据库的信息,知道为哪张表开启事务 -->
        <property name="dataSource" ref="myDataSource"/>
    </bean>
  1. 声明方法需要的事务的类型,就是事务的属性包括传播行为,隔离级别,时限等 【注解的方式只要一个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"/>
  1. 配置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

  1. 要使用这个监听器,要先加入依赖,就是加入spring-web
<!-- 在服务器使用spring -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-web</artifactId>
      <version>5.3.14</version>
    </dependency>
  1. 加入后就可以使用这个监听器了,接着就是在web.xml文件中进行监听器的配置;【tomcat容器】,这里输入contextLoadListener即可;监听器创建后会读取spring的配置文件,因为自动创建spring的容器对象,需要去读取文件创建容器; 但是这里默认时读取WEB-INF/applicationcontext.xml; 路径不正确,所以接下来需要配置路径
<listener>
          <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
      </listener>
  1. 配置路径使用context-param标签即可;选择configLoaction — 表示的就是spring配置文件的位置;路径时在类路径下面;所以加上classpath:
<context-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:springconfig.xml</param-value>
 </context-param>
  1. 从全局作用域中获取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的框架🎄

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值