Spring

Spring

Spring 是一个 IOC(DI) 和 AOP 容器框架.

一、浅谈 Spring

二、搭建 Spring 开发环境:

三、IOC & DI 概述

四、获取 Bean 实例

1.从 IOC 容器中获取 Bean

在 spring 配置文件中配置:


    <bean id="helloword" class="cn.edu.pzhu.cg.helloword.HelloWord">
        <property name="name" value="Spring"></property>
    </bean>

获取 bean 实例:


  public static void main(String[] args) {

        //创建 Spring 的 IOC 对象
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");

        //从 IOC 对象中获取 Bean 实例
        HelloWord helloWord = (HelloWord) ctx.getBean("helloword");

        helloWord.hello();
    }
2.通过 P 命名空间为 bean 赋值:

    <!-- 可以通过 p 命名空间为 bean 赋值,需要先导入 p 的 命名空间 -->
    <bean id="helloword2" class="cn.edu.pzhu.cg.helloword.HelloWord" p:name="CG"></bean>
3.抽象 bean:

    <!-- 抽象bean:bean 的 abstract 属性为 true 的bean。这样的 bean 不能被 IOC 容器实例化,只能用来被继承。 -->
    <bean id="abstracPerson" class="cn.edu.pzhu.cg.test.Person"
          p:name="CG" p:age="22" abstract="true"></bean>


    <!-- bean 配置的继承:使用 bean 的 parent 属性,指定继承那个 bean 的配置 -->
    <bean id="person2" p:name="Tom" parent="person1"></bean>
4.引用其他 bean:

5.bean 的作用域:

使用 bean 的scope属性来配置 bean 的作用域:

  1. singleton:默认的。容器初始化时创建 bean 实例,在整个容器的生命周期内,只创建这一个 bean,单例的。
  2. prototype:原型的。容器初始化时不创建 bean 实例,而在每次请求时都创建一个新的 bean 实例,并返回。

    <bean id="person3" class="cn.edu.pzhu.cg.test.Person" scope="prototype"></bean>
6.使用外部属性文件:

外部属性文件 db.properties:


    user=root
    password=123456
    driverclass=com.mysql.jdbc.Driver
    jdbcurl=jdbc:mysql:///test2

在配置文件中引用属性文件:


    <!-- 导入属性文件 -->
    <context:property-placeholder location="classpath:db.properties"/>


    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <!-- 使用外部化属性文件 的属性值 -->
        <property name="user" value="${user}"></property>
        <property name="password" value="${password}"></property>
        <property name="driverClass" value="${driverclass}"></property>
        <property name="jdbcUrl" value="${jdbcurl}"></property>
    </bean>
7.Spring表达式语言:spel


    <bean id="address" class="cn.edu.pzhu.cg.spel.Address">
        <property name="province"  value="#{'四川'}"/>
        <property name="city"  value="#{'攀枝花'}"/>
    </bean>
    <bean id="car" class="cn.edu.pzhu.cg.spel.Car">
        <property name="name" value="#{'Jeep'}"></property>
        <property name="price" value="#{'400000'}"></property>

        <!-- 使用 spel 引用类的静态属性 -->
        <property name="tyrePerimeter" value="#{T(java.lang.Math).PI * 80}"></property>

    </bean>

    <bean id="person" class="cn.edu.pzhu.cg.spel.Person">
        <property name="name" value="张超"></property>
        <property name="age" value="22"></property>

        <!-- 使用 spel 来引用其他 Bean 的属性 -->
        <property name="city" value="#{address.city}"></property>

        <!-- 使用 spel 来引用其他 Bean -->
        <property name="car" value="#{car}"></property>

        <!-- 在 spel 中使用运算符 -->
        <property name="info" value="#{car.price > 500000 ? '金领':'白领'}"></property>
    </bean>

8.IOC 容器中 Bean 的生命周期方法:


    <bean id="car" class="cn.edu.pzhu.cg.cycle.Car"
           init-method="init"
           destroy-method="destroy">

    <property name="brand" value="Jeep"></property>    
    </bean>

        public class Car {

        private String brand;

        public Car() {
            super();
            System.out.println("Car's constructor");
        }

        public String getBrand() {
            return brand;
        }

        public void setBrand(String brand) {
            this.brand = brand;
        }
        public void init() {
            System.out.println("Init method");
        }

        public void destroy() {
            System.out.println("Destroy method");
        }
      }
9.创建 Bean 后置处理器:


BeanPostProcessor 实现类:

    import org.springframework.beans.BeansException;
    import org.springframework.beans.factory.config.BeanPostProcessor;

    public class MyBeanPostProcessor implements BeanPostProcessor{

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("postProcessAfterInitialization : " + bean + ", " + beanName);
        Car car = new Car();
        car.setBrand("BWM");
        return car;
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("postProcessBeforeInitialization : " + bean + ", " + beanName);
        return bean;
    }

}

bean.xml:

    <!-- 
        实现BeanPostProcessor 接口,并具体提供:
            1.Object postProcessBeforeInitialization(Object bean, String beanName):在 init-method 之前被调用
            2.Object postProcessAfterInitialization(Object bean, String beanName):在 init-method 之后被调用
        的实现。
        参数:
            bean: bean 实例本身。
            beanName: IOC 容器配置的 bean 的名字。
            返回值:是实际上返回给用户的那个 bean,注意:可以在以上两个方法中修改返回的 bean,甚至返回一个新的 bean。

     -->
    <!-- 配置 Bean 的后置处理器 :不需要配置 id,IOC 容器自动识别 BeanPostProcessor -->
    <bean class="cn.edu.pzhu.cg.cycle.MyBeanPostProcessor"></bean>

10.通过调用静态工厂方法创建 Bean:

通过访问静态的工厂方法来获得一个 bean 实例

静态工厂方法:

    public class StaticCarFactory {
        private static Map<String, Car> cars = new HashMap();
        static {
            cars.put("Audi", new Car("Audi", 300000));
            cars.put("Jeep", new Car("Jeep", 400000));
            //....
        }
        public static Car getCar(String name) {
            return cars.get(name);
        }
    }
bean.xml:

    <!-- 通过静态工厂方法来配置 bean,注意不是配置静态工厂方法实例,而是配置 bean 实例 -->

    <!--  
        class : 指向静态工厂方法的全类名。
        factory-method : 指向静态工厂方法的名字(方法是静态的)。
        constructor-arg : 如果工厂方法需要传入参数,则使用 constructor-arg 来配置参数。

    -->

    <bean id="car1" class="cn.edu.pzhu.cg.factory.StaticCarFactory"
        factory-method="getCar">
        <constructor-arg value="Jeep"></constructor-arg>    
    </bean>
11.实现 FactoryBean 接口在 Spring IOC 容器中配置 Bean:
工厂方法:

    /*
     * 自定义的 factorybean 需要实现 FactoryBean 接口
     */
    public class CarFactoryBean implements FactoryBean<Car>{

        private String brand;
        public void setBrand(String brand) {
            this.brand = brand;
        }

        //返回 bean 的对象
        @Override
        public Car getObject() throws Exception {
            return new Car(brand, 300000);
        }

        //返回 bean 的类型
        @Override
        public Class<?> getObjectType() {
            return Car.class;
        }

        // 返回bean 是否是单例
        @Override
        public boolean isSingleton() {
            return true;
        }

    }
bean.xml:

    <!--  
        通过 FactoryBean 来配置 bean 的实例。
        class : 指向 FactoryBean 的全类名。
        property : 配置 FactoryBean 的属性。

        但实际上返回是 FactoryBean 中 getObject() 方法所返回的实例! 
    -->
    <bean id="car" class="cn.edu.pzhu.cg.factorybean.CarFactoryBean">
        <property name="brand" value="Jeep"></property>
    </bean>
12.在 classpath 中扫描组件


    <!-- 指定 Spring IOC 容器扫描的包 -->
    <!-- resource-pattern:过滤特定的类 -->

    <context:component-scan base-package="cn.edu.pzhu.cg.annotation" resource-pattern="controller/*.class">
    </context:component-scan>


     <!--  
        context:exclude-filter:子节点指定排除哪些指定表达式的组件

        context:include-filter:子节点指定包含哪些表达式的组件,该子节点需要和 use-default-filters 配合使用
     -->
      <context:component-scan base-package="cn.edu.pzhu.cg.annotation" use-default-filters="false">
        <!--基于注解-->

        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>


        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>


        <!--基于类名-->

        <context:exclude-filter type="assignable" expression="cn.edu.pzhu.cg.annotation.service.UserService"/>


        <context:include-filter type="assignable" expression="cn.edu.pzhu.cg.annotation.service.UserService"/>
     </context:component-scan>
13.使用注解自动装配bean:

为类加上注解,不需要在 bean.xml 中配置也可以被 IOC 装配



五、Spring AOP

1.AOP 简介

2.AOP 术语

3.Spring AOP

  • AspectJ:Java 社区里最完整最流行的 AOP 框架.

  • 在 Spring2.0 以上版本中, 可以使用基于 AspectJ 注解或基于 XML 配置的 AOP

4.在 Spring 中启用 AspectJ 注解支持

5.用 AspectJ 注解声明切面

6.AOP 日志

(1).前置通知

(2).后置通知

(3).返回通知

(4).异常通知

(5).环绕通知

7.指定切面的优先级

8.重用切入点定义

实例代码:


    import java.util.Arrays;
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.After;
    import org.aspectj.lang.annotation.AfterReturning;
    import org.aspectj.lang.annotation.AfterThrowing;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.aspectj.lang.annotation.Pointcut;
    import org.springframework.core.annotation.Order;
    import org.springframework.stereotype.Component;

    /*
     * 1.先加入 IOC 容器
     * 2.在声明为切面
     * 3.使用 @Order() 注解,可以指定切面的优先级,值越小优先级越高。
     */
    @Order(2)
    @Component
    @Aspect
    public class LoggingAspect {

        /*
         * 定义一个方法,用于声明切入点表达式,一般情况,该方法中不需要添加其他代码。(根据不同的位置,可以在前面指定包名、类名)
         * 使用 @Pointcut 来声明切入点表达式
         */
        @Pointcut("execution(public int cn.edu.pzhu.cg.aop.impl.ArithmeticCalculatorImpl.*(int, int))")
        public void declareJointPointExpression(){}

        //前置通知:在目标方法执行前通知
        @Before("declareJointPointExpression()")
        public void beforeMethod(JoinPoint joinpoint) {
            String methodName = joinpoint.getSignature().getName();
            Object[] args = joinpoint.getArgs();

            System.out.println("The method "+methodName +" begin with " + Arrays.asList(args));
        }

        //后置通知:在目标方法执行后通知,无论目标方法是否发生异常,都会通知
        //注意:在后置通知中,不能访问目标方法执行的结果   
        @After("declareJointPointExpression()")
        public void afterMethod(JoinPoint joinpoint) {
            String methodName = joinpoint.getSignature().getName();

            System.out.println("The method "+methodName +" end ");
        }

        /*
         * 在方法正常结束后执行的;并且可以访问到方法的返回值。
         */
        @AfterReturning(value="declareJointPointExpression()" ,returning="result")
        public void afterReturning(JoinPoint joinpoint,Object result) {
            String methodName = joinpoint.getSignature().getName();

            System.out.println("The method "+methodName +" end with " + result);

        }

        /*
         * 目标出现异常后进行通知;可以访问到异常对象;且可以指定在出现特定的异常时执行通知。
         */
        @AfterThrowing(value="declareJointPointExpression()",throwing="ex")
        public void afterException(JoinPoint joinpoint,Exception ex) {
            //可以改变形参来指定某种异常的通知,比如 NullPointerException,出现空指针时才通知。 
            String methodName = joinpoint.getSignature().getName();

            System.out.println("The method "+methodName +" occurs:  " + ex);
        }
        /*
         * 1.环绕通知需要携带 ProceedingJoinPoint 类型的参数
         * 2.环绕通知类似于动态代理的全过程:ProceedingJoinPoint 参数可以决定是否执行目标方法
         * 3.环绕通知必须有返回值,返回值即为目标方法的返回值
         */
        @Around("execution(public int cn.edu.pzhu.cg.aop.impl.ArithmeticCalculatorImpl.*(int, int))")
        public Object aroundMethod(ProceedingJoinPoint pjp) {
            Object result = null;
            String methodName = pjp.getSignature().getName();
            try {
                //前置通知
                System.out.println("The method "+methodName +" begin with " + Arrays.asList(pjp.getArgs()));

                //执行方法
                result = pjp.proceed();

                //结果通知
                System.out.println("The method "+methodName +" end with " + result);    
            } catch (Throwable e) {
                //异常通知
                System.out.println("The method "+methodName +"  occurs exception:" + e);
                throw new RuntimeException(e);
            }
            //后置通知
            System.out.println("The method "+methodName +" end ");
            return result;
        }   
    }

六、Spring 对 JDBC 的支持

1.JdbcTemplate 简介

  • 为了使 JDBC 更加易于使用, Spring 在 JDBC API 上定义了一个抽象层, 以此建立一个 JDBC 存取框架.
  • 作为 Spring JDBC 框架的核心, JDBC 模板的设计目的是为不同类型的 JDBC 操作提供模板方法. 每个模板方法都能控制整个过程, 并允许覆盖过程中的特定任务. 通过这种方式, 可以在尽可能保留灵活性的情况下, 将数据库存取的工作量降到最低.

2.NamedParameterJdbcTemplate 简介

  • 在经典的 JDBC 用法中, SQL 参数是用占位符 ? 表示,并且受到位置的限制. 定位参数的问题在于, 一旦参数的顺序发生变化, 就必须改变参数绑定.
  • 在 Spring JDBC 框架中, 绑定 SQL 参数的另一种选择是使用具名参数(named parameter).
  • 具名参数: SQL 按名称(以冒号开头)而不是按位置进行指定. 具名参数更易于维护, 也提升了可读性. 具名参数由框架类在运行时用占位符取代
  • 具名参数只在 NamedParameterJdbcTemplate 中得到支持

在 XML 文件中配置 JdbcTemplate :


    <?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:context="http://www.springframework.org/schema/context"
        xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">

        <!-- 导入资源文件 -->
        <context:property-placeholder location="classpath:db.properties"/>

        <!-- 配置 C3P0 数据源 -->
        <bean id="dataSource"
                class="com.mchange.v2.c3p0.ComboPooledDataSource">

            <property name="user" value="${user}"></property>
            <property name="password" value="${password}"></property>
            <property name="jdbcUrl" value="${jdbcurl}"></property>
            <property name="driverClass" value="${driverClass}"></property>

            <property name="initialPoolSize" value="${initialPoolSize}"></property>
            <property name="maxPoolSize" value="${maxPoolSize}"></property>
        </bean>

        <!-- 配置 Spring 的 JdbcTemplate -->
        <bean id="jdbcTemplate"
                class="org.springframework.jdbc.core.JdbcTemplate">

            <property name="dataSource" ref="dataSource"></property>        
        </bean>

        <!-- 配置能使用具名参数的 NamedParameterJdbcTemplate -->
        <bean id="namedParameterJdbcTemplate"
                class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate">
            <constructor-arg ref="dataSource"></constructor-arg>
        </bean>
    </beans>

实例代码:


    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;  
    import javax.sql.DataSource; 
    import org.junit.Test;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    import org.springframework.jdbc.core.BeanPropertyRowMapper;
    import org.springframework.jdbc.core.JdbcTemplate;
    import org.springframework.jdbc.core.RowMapper;
    import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource;
    import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
    import org.springframework.jdbc.core.namedparam.SqlParameterSource;


    public class JDBCTest {

        private ApplicationContext ctx = null;
        private JdbcTemplate jdbcTemplate = null;
        private NamedParameterJdbcTemplate namedParameterJdbcTemplate = null;
        {
            ctx = new ClassPathXmlApplicationContext("bean_jdbc.xml");
            jdbcTemplate = (JdbcTemplate) ctx.getBean("jdbcTemplate");
            namedParameterJdbcTemplate = ctx.getBean(NamedParameterJdbcTemplate.class);
        }

        /*
         * 使用具名参数时,可以用 update(String sql, SqlParameterSource paramSource) 方法进行更新操作
         * 1.SQL 参数的参数名和类的属性名一致。
         * 2.使用 SqlParameterSource 的 BeanPropertySqlParameterSource 实现类作为参数。
         */
        @Test
        public void testNamedParameterJdbcTemplate2() {
            String sql = "INSERT INTO student(name,age,sno) VALUES(:name,:age,:sno)";
            Student student = new Student();
            student.setName("张学友");
            student.setAge(50);
            student.setSno("10002");

            SqlParameterSource paramSource = new BeanPropertySqlParameterSource(student);
            namedParameterJdbcTemplate.update(sql, paramSource);
        }

        /*
         * NamedParameterJdbcTemplate 可以使用具名参数
         * 可为参数取别名:
         *  1.好处:若有多个参数,则不用再去对应位置,直接对应参数名,便于维护。
         *  2.缺点:较为麻烦。
         */
        @Test
        public void testNamedParameterJdbcTemolate() {
            String sql = "INSERT INTO student(name,age,sno) VALUES(:name,:age,:sno)";

            Map<String, Object> paramMap = new HashMap<>();
            paramMap.put("name", "刘德华");
            paramMap.put("age", 50);
            paramMap.put("sno", "10001");

            namedParameterJdbcTemplate.update(sql, paramMap);

        }

        /*
         * 获取单个列的值
         */
        @Test
        public void testQueryForObject2() {
            String sql = "SELECT count(id) FROM student";
            long count = jdbcTemplate.queryForObject(sql, long.class);
            System.out.println(count);


        }

        /*
         * 查到实体类的集合
         */
        @Test
        public void testQueryForList() {
            String sql = "SELECT id,name,age,sno FROM student WHERE id > ?";

            RowMapper<Student> rowMapper = new BeanPropertyRowMapper<>(Student.class);
            List<Student> students = jdbcTemplate.query(sql, rowMapper,6);

            System.out.println(students);
        }


        /*
         * 从数据库中获取一条记录,实际上是得到对应的一个对象。
         * 需要调用 queryForObject(String sql, RowMapper<Student> rowMapper, Object... args) 方法
         *  1.其中 RowMapper 指定如何去映射结果集的行,常用的实现类为 BeanPropertyRowMapper。
         *  2.使用 SQL 中列的别名完成列名和类中属性名的映射,比如在 表中列名 为 last_name,在类 中属性名为 lastname
         *       在写 SQL 语句时,应该这样: SELECT last_name lastname FROM student WHERE id = ?
         * 
         *  3.不支持级联属性。JdbcTemplate 是一个 JDBC 小工具,而不是 ORM 框架。
         * 
         */
        @Test
        public void testQueryForObject() {
            String sql = "SELECT id,name,age,sno FROM student WHERE id = ?";

            RowMapper<Student> rowMapper = new BeanPropertyRowMapper<>(Student.class);

            Student student = jdbcTemplate.queryForObject(sql, rowMapper, 6);

            System.out.println(student);
        }

        /*
         * 进行批量的 INSERT UPDATE DELETE 
         * batchUpdate 中第二个参数是 Object[] 的 List ,因为一条记录对应一个 Object 数组,多条记录就是一个 List. 
         */
        @Test
        public void testBatchUpdate() {
            String sql = "INSERT INTO student(name,age,sno) VALUES(?,?,?)";

            List<Object[]> batchArgs = new ArrayList<>();
            batchArgs.add(new Object[] {"张三",18,"201510803001"});
            batchArgs.add(new Object[] {"李四",25,"201510803002"});
            batchArgs.add(new Object[] {"翠花",20,"201510803003"});
            batchArgs.add(new Object[] {"小华",21,"201510803004"});
            batchArgs.add(new Object[] {"小明",20,"201510803005"});

            jdbcTemplate.batchUpdate(sql, batchArgs);

        }

        /*
         * 执行 UPDATE INSERT DELETE
         */
        @Test
        public void testUpdate() {
            String sql = "UPDATE student SET age = ? WHERE id = ?";
            jdbcTemplate.update(sql, 20,8);


        }

        @Test
        public void testDataSource() throws Exception {
            DataSource dataSource = ctx.getBean(DataSource.class);
            System.out.println(dataSource.getConnection());
        }

    }

七、Spring 中的事务管理

1. Spring 中的事务

  • 作为企业级应用程序框架, Spring 在不同的事务管理 API 之上定义了一个抽象层. 而应用程序开发人员不必了解底层的事务管理 API, 就可以使用 Spring 的事务管理机制.
  • Spring 既支持编程式事务管理, 也支持声明式的事务管理.
  • 编程式事务管理: 将事务管理代码嵌入到业务方法中来控制事务的提交和回滚. 在编程式管理事务时, 必须在每个事务操作中包含额外的事务管理代码.
  • 声明式事务管理: 大多数情况下比编程式事务管理更好用. 它将事务管理代码从业务方法中分离出来, 以声明的方式来实现事务管理. 事务管理作为一种横切关注点, 可以通过 AOP 方法模块化. Spring 通过 Spring AOP 框架支持声明式事务管理.

2.用 @Transactional 注解声明式地管理事务

3.事务传播属性

  • 当事务方法被另一个事务方法调用时, 必须指定事务应该如何传播. 例如: 方法可能继续在现有事务中运行, 也可能开启一个新事务, 并在自己的事务中运行.
  • 事务的传播行为可以由传播属性指定. Spring 定义了 7 种类传播行为.

4.事务的隔离级别

  • 从理论上来说, 事务应该彼此完全隔离, 以避免并发事务所导致的问题. 然而, 那样会对性能产生极大的影响, 因为事务必须按顺序运行.
  • 在实际开发中, 为了提升性能, 事务会以较低的隔离级别运行.
  • 事务的隔离级别可以通过隔离事务属性指定

Spring 支持的事务隔离级别:

  • 事务的隔离级别要得到底层数据库引擎的支持, 而不是应用程序或者框架的支持.
  • Oracle 支持的 2 种事务隔离级别:READ_COMMITED , SERIALIZABLE
  • Mysql 支持 4 中事务隔离级别

5.设置隔离事务属性

6.设置回滚事务属性

7.超时和只读属性

  • 由于事务可以在行和表上获得锁, 因此长事务会占用资源, 并对整体性能产生影响.
  • 如果一个事物只读取数据但不做修改, 数据库引擎可以对这个事务进行优化.
  • 超时事务属性: 事务在强制回滚之前可以保持多久. 这样可以防止长期运行的事务占用资源.
  • 只读事务属性: 表示这个事务只读取数据但不更新数据, 这样可以帮助数据库引擎优化事务.

实例代码:


    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Isolation;
    import org.springframework.transaction.annotation.Propagation;
    import org.springframework.transaction.annotation.Transactional;

    @Service("bookShopService")
    public class BookShopServiceImpl implements BookShopService {

        @Autowired
        private BookShopDao bookShopDao;

        /*
         * 1.使用 propagation 指定事务的传播行为,即当前的事务方法在被另一个事务方法调用时,如何使用事务。
         * 2.默认,REQUIRED:使用调用方法的事务;
         *        REQUIRES_NEW:使用自己的事务,调用方法的事务被挂起。
         * 3.使用 isolation 指定事务的隔离级别,最常用的是 READ_COMMITTED(读已提交)
         * 4.使用 timeout 可以指定强制回滚前事务可以占用的最长时间,单位为秒 
         */
        @Transactional(propagation=Propagation.REQUIRES_NEW,isolation=Isolation.READ_COMMITTED,
                        timeout=2)
        @Override
        public void purchase(int userId, int bookId) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {}
            //1.获取书的单价
            int price = bookShopDao.getBookPriceByBookId(bookId);

            //2.修改书的库存
            bookShopDao.updateBookStock(bookId);

            //3.修改用户余额
            bookShopDao.updateUserAccount(userId, price);
        }

    }

八、Spring 整合 Hibernate

需要导入 Spring 和 Hibernate 所需的 jar 包。

1.在 Spring 中配置 SessionFactory

  • 对于 Hibernate 而言, 必须从原生的 Hibernate API 中构建 SessionFactory. 此外, 应用程序也无法利用 Spring 提供的数据存储机制(例如: Spring 的事务管理机制)
  • Spring 提供了对应的工厂 Bean, 可以用单实例的形式在 IOC 容器中创建 SessionFactory 实例.

2.各种 xml 文件的配置

(1).hibernate.cfg.xml:在 hibernate 的配置文件中只配置 hibernate 基本信息。

    <hibernate-configuration>
        <session-factory>
            <!-- 配置 Hibernate 的基本信息 -->
            <property name="hibernate.dialect">org.hibernate.dialect.MySQLInnoDBDialect</property>
            <property name="hibernate.show_sql">true</property>
            <property name="hibernate.format_sql">true</property>
            <property name="hbm2ddl.auto">update</property>
        </session-factory>
    </hibernate-configuration>

(2).Spring 配置文件:

  如果在 Spring IOC 容器中配置数据源. 可以将该数据源注入到 LocalSessionFactoryBean 的 dataSource 属性中. 该属性可以指定的数据源会覆盖掉 Hibernate 配置文件里的数据库配置.


    <!-- 配置 C3P0 数据源 -->
        <bean id="dataSource"
                class="com.mchange.v2.c3p0.ComboPooledDataSource">

            <property name="user" value="${user}"></property>
            <property name="password" value="${password}"></property>
            <property name="driverClass" value="${driverclass}"></property>
            <property name="jdbcUrl" value="jdbc:mysql:///test2?useUnicode=true&amp;characterEncoding=UTF-8"></property>

            <property name="initialPoolSize" value="${initialPoolSize}"></property>
            <property name="maxPoolSize" value="${maxPoolSize}"></property>
        </bean>

        <!-- 配置 SessionFactory  -->
        <bean id="sessionFactory" 
                class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
            <property name="dataSource" ref="dataSource"></property>

            <!-- 关联 Hibernate 的配置文件 -->
            <property name="configLocation" value="classpath:hibernate.cfg.xml"></property>

            <!-- 关联 Hibernate 的映射配置文件 -->
            <property name="mappingLocations" value="classpath:cn/edu/pzhu/entities/*.hbm.xml"></property>  
        </bean>

可以将 Hibernate 配置信息放在 Spring 的配置文件中,这样可以少维护一个配置文件,只需利用 hibernateProperties 属性即可

(3).配置事务:

  为了让 Hibernate 使用上 Spring 的声明式事务(也可以使用注解),需要配置事务。需要在事务管理器中引用前面的配置的 sessionFactory。



        <!-- 配置事务管理器 -->
        <bean id="transactionManager"
                class="org.springframework.orm.hibernate5.HibernateTransactionManager">
            <property name="sessionFactory" ref="sessionFactory"></property>
        </bean>

        <!--配置事务-->
        <tx:advice id="txAdvice" transaction-manager="transactionManager">
            <tx:attributes>
                <tx:method name="get*" read-only="true"/> 
                <tx:method name="*"/>
            </tx:attributes>
        </tx:advice>

        <!--配置切点信息,并与事务关联-->
        <aop:config>
            <aop:pointcut expression="execution(* cn.edu.pzhu.dao.*.*(..))" id="txPointcut"/>
            <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
        </aop:config>


        <!-- 启用事务注解 -->
        <tx:annotation-driven transaction-manager="transactionManager"/> 

通过以上配置就可以在 Spring 中使用 Hibernate,同时可以为 Hibernate 使用上 Spring 的声明式事务。

3.使用 SessionFactory

  在hibernate和spring集成之后,如果我们使用spring配置注入的sessionFactory生成session,这个时候我们一定要注意使用getCurrentSession()而不要使用openSession,这是因为spring去管理事务,getCurrentSession()始终返回的是同一个对象,当出问题的时候,事务会自动回滚,而openSession()则返回不同的对象,出了问题,由于是不同的session,就会发生不能自动回滚的现象,所以使用的时候一定要注意!


    import org.hibernate.Session;
    import org.hibernate.SessionFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    import cn.edu.pzhu.entities.Person;

    @Service("personDao")
    public class PersonDaoImpl implements PersonDao {

        @Autowired
        private SessionFactory sessionFactory;
        private Session getSession() {
            //从当前线程中去获取 session
            return sessionFactory.getCurrentSession();
        }

        @Override
        public String getPersonById(int id) {

            String hql = "SELECT personName FROM Person where id = ?";

            return getSession().createQuery(hql).setParameter(0, id).uniqueResult().toString();
        }

        @Transactional
        @Override
        public void save(Person person){
            getSession().save(person);
            //手动抛出异常测试事务配置是否成功          
            throw new RuntimeException("异常");
        }

    }

九、Spring 整合 Struts2

1.导入 jar 包:

  • 在 web 环境下使用 Spring 需要导入一下两个 jar 包:

    spring-web-4.3.3.RELEASE.jar

    spring-webmvc-4.3.3.RELEASE.jar

  • 除了 Struts 和 Spring 的 jar 包,需要额外导入:

    struts2-spring-plugin-2.3.24.1.jar

2.在通用的 web 应用中访问 Spring

(1).需要额外的加入以下 jar 包:
    spring-web-4.3.3.RELEASE.jar
    spring-webmvc-4.3.3.RELEASE.jar

(2).Spring 的配置文件和非 WEB 环境下没有什么不同

(3).需要在 web.xml 中加入如下配置:
    <!-- 配置 Spring 配置文件的名称和位置 -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:applicationContext.xml</param-value>
    </context-param>

    <!-- 配置启动 IOC 容器的 ServletContextListener -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

3.web.xml 文件示例代码


    <!-- 配置 spring 配置文件的位置 -->
      <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:applicationContext.xml</param-value>
      </context-param>


      <!-- 配置spring的监听器 -->
      <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener
            </listener-class>
      </listener>

      <filter>
        <filter-name>struts2</filter-name>
        <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
      </filter>
      <filter-mapping>
        <filter-name>struts2</filter-name>
        <url-pattern>/*</url-pattern>
      </filter-mapping>

4.在 web 应用程序中访问 Spring 的 ApplicationContext 对象

5.将 Struts 的 Action 交由 Spring 掌管,通过 IOC 实现自动注入。

  在 Spring 的配置文件中配置 Action 时要加上 scope=”prototype”,非单例模式。

示例代码:


        <!-- 在配置 Action 时候应该 scope="prototype" 配置为非单例模式-->
        <bean id="springAction" 
                class="cn.edu.pzhu.action.SpringAction" scope="prototype"></bean> 

  在 Struts 的配置文件配置 Action 时,class 应该指向在 Spring 配置文件中当前 Action 的 id,而不再是 Action 的全类名。

示例代码:


    <struts>
        <constant name="struts.action.extension" value="action,do"></constant>
        <package name="default" namespace="/" extends="struts-default">
            //注意:这里 class 不再指向 Action 的全类名,因为 Action 已经交由 Spring 掌管了。
            <action name="SpringAction" class="springAction" method="Hello">
                <result name="success">success.jsp</result>
            </action>       
        </package>
    </struts>

通过以上配置就可以在 Spring 中使用 Struts 了,将 Action 交由 Spring 掌管,实现自动注入。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值