Spring第三天
朱越就
1. AOP的相关概念
1.1. AOP概述
1.1.1. 什么是AOP, 面向切面编程
AOP为Aspect Oriented Programming的缩写, 意为:面向切面编程, 通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术. AOP是OOP的延续, 是软件开发中的一个热点, 也是Spring框架中的一个重要内容, 是函数式编程的一种衍生范型. 利用AOP可以对业务逻辑的各个部分进行隔离, 从而使得业务逻辑各部分之间的耦合度降低, 提高程序的可重用性, 同时提高了开发的效率.
1.1.2. 传统开发模型: 纵向的编程.
1.1.3. 面向切面编程: 纵横配合的编程.
1.1.4. AOP的作用及优势
作用:
在程序运行期间,不修改源码对已有方法进行增强。
优势:
减少重复代码、提高开发效率、维护方便
1.1.5. AOP的实现方式
使用动态代理模式来实现
可能通过上面的介绍,我们还是没有一个清晰的认识。没关系,我们看看下面的具体应用。
1.2. AOP的具体应用
1.2.1. 案例中问题
通过下面的实现代码,我们能看出什么问题吗?
--接口代码 /** * 学生的业务层接口 */ public interface StudentService { /** * 保存学生 * @param student */ void saveStudent(Student student); /** * 查询所有学生 * @return */ List<Student> findAllStudent(); /** * 删除学生 * @param student */ void removeStudent(Student student); /** * 根据id查询学生 * @param custId * @return */ Student findStudentById(Long custId); /** * 修改学生 * @param student */ void updateStudent(Student student); } --实现代码 /** * 学生的业务层实现类 * 事务必须在此控制 * 业务层都是调用持久层的方法 */ public class StudentServiceImpl implements StudentService { private StudentDAO studentDAO = new StudentDAOImpl(); @Override public void saveStudent(Student student) { Session s = null; Transaction tx = null; try{ s = HibernateUtils.getCurrentSession(); tx = s.beginTransaction(); studentDAO.saveStudent(student); tx.commit(); }catch(Exception e){ tx.rollback(); throw new RuntimeException(e); } } @Override public List<Student> findAllStudent() { Session s = null; Transaction tx = null; try{ s = HibernateUtils.getCurrentSession(); tx = s.beginTransaction(); List<Student> students = studentDAO.findAllStudent(); tx.commit(); return students; }catch(Exception e){ tx.rollback(); throw new RuntimeException(e); } } @Override public void removeStudent(Student student) { Session s = null; Transaction tx = null; try{ s = HibernateUtils.getCurrentSession(); tx = s.beginTransaction(); studentDAO.removeStudent(student); tx.commit(); }catch(Exception e){ tx.rollback(); throw new RuntimeException(e); } } @Override public Student findStudentById(Long custId) { Session s = null; Transaction tx = null; try{ s = HibernateUtils.getCurrentSession(); tx = s.beginTransaction(); Student c = studentDAO.findStudentById(custId); tx.commit(); return c; }catch(Exception e){ tx.rollback(); throw new RuntimeException(e); } } @Override public void updateStudent(Student student) { Session s = null; Transaction tx = null; try{ s = HibernateUtils.getCurrentSession(); tx = s.beginTransaction(); studentDAO.updateStudent(student); tx.commit(); }catch(Exception e){ tx.rollback(); throw new RuntimeException(e); } } } |
上面代码的问题就是:我们的事务控制的代码是重复性的。这还只是一个业务类,如果有多个业务了,每个业务类中都会有这些重复性的代码。是不是重复代码太多了?
思考:我们有什么办法解决这个问题吗?
要想解决这个问题,我们得回顾一下之前我们讲过的知识:动态代理模式。
1.2.2. 动态代理模式
使用JDK官方的Proxy类创建代理对象
- 需要通过Proxy类创建代理对象
- 创建代理对象必须要一个代理处理类(实现了接口InvocationHandler的类)
1.2.2.1. 说明
很久之前,工厂是小作坊。客户是直接和工厂见面,购买商品的。
随着时间的推移,工厂慢慢做大了。工厂的产品统一通过经销商代理出售了,这个时候客户需要购买工厂的商品,只能通过经销商购买。我们通过以下的代码来演示该流程。
1.2.2.2. 示例代码
- 创建一个产品接口
package com.sxt.proxy; /** * 工厂生产的商品 * 要求有零售价和批发价 * @author ranger * */ public interface IProduct { //1.零售价格 public void sale(float money); //2.批发价格 public void wholesale(float money); } |
- 创建一个产品实现类
package com.sxt.proxy; public class Product implements IProduct { @Override public void sale(float money) { System.out.println("零售价:"+money); } @Override public void wholesale(float money) { System.out.println("批发价:"+money); } } |
3.创建一个经销商类
package com.sxt.proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class Franchiser implements InvocationHandler{ private Object source; /** * 创建代理对象,必须传入原对象 * @param source * @return */ public Object createProxy(Object source) { this.source=source; //通过Proxy创建代理对象,需要interfaces Class<?>[] interfaces = source.getClass().getInterfaces(); //通过Proxy创建代码对象,需要classLoader ClassLoader classLoader = source.getClass().getClassLoader(); //通过Proxy创建代理对象 /* * newProxyInstance * Proxy的newProxyInstance负责给被代理对象创建代理对象 * 参数1:ClassLoader(告诉JVM到什么地方找寻该类) * 参数2:Class[](一个接口数组,实现该类实现的所有接口) * 参数3:代理处理类(实现了InvocationHandler接口的对象都是代理处理类) * 传入this,Franchiser就是一个实现了InvocationHandler接口的类 */ //由Proxy生产出来的代理对象objProxy,继承了被代理类的方法,实现了被代理类的接口,并由代理处理类执行代理逻辑。 return Proxy.newProxyInstance(classLoader, interfaces, this); } /** * 参数: * 1.proxy,代理对象的引用。不一定每次都用得到 * 2.method,当前执行的方法对象 * 3.args,执行方法所需的参数 * 返回值: * 当前执行方法的返回值 */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println(proxy.getClass()); //获得方法名 String name = method.getName(); //方法的第一个参数 Float money = (Float) args[0]; Object invoke =null; if (name.equals("sale")) { if(money>=3.0f){ // 反射的method.invoke(...)方法,表示真正执行 invoke = method.invoke(source, money); }else{ System.out.println("-零售没有货,对不起-"); } } if(name.equals("wholesale")){ if(money>=2.0f){ invoke = method.invoke(source, money); }else{ System.out.println("-批发没有货,对不起-"); } } return invoke; } } |
- 创建一个客户类调用
package com.sxt.proxy; public class Client { public static void main(String[] args) { //1.没有经销商的情况 //IProduct product=new Product(); //被客户压价 //product.sale(2.0f); //product.wholesale(1.5f); //2.只能通过代理商获得商品 IProduct product=new Product(); //创建一个经销商的对象 Franchiser franchiser=new Franchiser(); IProduct proxyProduct = (IProduct)franchiser.createProxy(product); proxyProduct.sale(2.0f); proxyProduct.wholesale(2.5f); } } |
这个故事(示例)讲完之后,我们从中受到什么启发呢?它到底能应用在哪呢?
有没有想到,将1.2.1案例中重复的代码使用动态代理模式来解决它。
回顾代理模式
- 代理模式的作用:就是在创建一个对象的代理对象后,可以执行该对象的方法的时候,加入一段统一的处理代码。
- 代理模式的缺陷是,每一个类都需要写一个代理类
- 动态代理模式的好处,就是一个代理类可以代理多个类的对象
- 基于动态代理模式可以处理多个类型的类的对象,所以必须要使用到JDK的Proxy和InvocationHandler来说实现
代理模式必须要有三个要素
- 原对象
- 获得代理对象(Proxy)
- 编写代理拦截方法后统一处理的代码(InvocationHandler)
1.2.3. 解决案例中的问题
思路只有一个:使用动态代理技术创建客户业务层的代理对象,在执行StudentServiceImpl时,对里面的方法进行增强,加入事务的支持。
--将Service所有的方法的事务处理删除
package com.sxt.service.impl; import java.util.List; import com.sxt.dao.StudentDAO; import com.sxt.dao.impl.StudentDAOImpl; import com.sxt.entity.Student; import com.sxt.service.StudentService; /** * 学生的业务层实现类 事务必须在此控制 业务层都是调用持久层的方法 */ public class StudentServiceImpl implements StudentService { private StudentDAO customerDAO = new StudentDAOImpl(); @Override public void saveStudent(Student student) { customerDAO.saveStudent(student); } @Override public List<Student> findAllStudent() { List<Student> student = customerDAO.findAllStudent(); return student; } @Override public void removeStudent(Student student) { customerDAO.removeStudent(student); } @Override public Student findStudentById(Long sid) { Student c = customerDAO.findStudentById(sid); return c; } @Override public void updateStudent(Student student) { customerDAO.updateStudent(student); } } |
--通过动态代理对象将所有方法的统一处理写在代理对象里面
package com.sxt.handler; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import org.hibernate.Session; import org.hibernate.Transaction; import com.sxt.utils.HibernateUtils; public class TransantionHandler implements InvocationHandler { private Object source; //1创建代理对象 public Object createProxy(Object source) { //1.创建代理对象-必须需要类加载器 ClassLoader loader = source.getClass().getClassLoader(); //2.创建代理对象-必须需要知道类的接口 Class<?>[] interfaces = source.getClass().getInterfaces(); //3.创建代理对象-必须要知道代理处理类(InvocationHandler) //创建代理类之后,调用代理类的每一个方法都会执行InvocationHandler的invoke方法。 Object instance = Proxy.newProxyInstance(loader, interfaces, this); //必须需要将source对象使用一个成员变量接收,因为在invoke方法动态方法需要调用 this.source=source; return instance; } /** * 代理执行逻辑,执行代理类的每一个方法都会执行该方法 */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //1.获得操作会话 System.out.println("=获得当前线程的会话="); Session session = HibernateUtils.getCurrentSession(); //2.开启事务 System.out.println("=启动事务="); Transaction transaction = session.beginTransaction(); //3.调用原来方法的逻辑 Object invoke=null; try { System.out.println("=代理之前="); invoke = method.invoke(source, args); System.out.println("=代理之后="); //4.提交事务 System.out.println("=提交事务="); transaction.commit(); //5.关闭会话 System.out.println("=关闭会话="); HibernateUtils.close(); } catch (Exception e) { e.printStackTrace(); transaction.rollback(); } //动态调用的方法返回 return invoke; } } |
--通过这个示例可以看到,我们将事务处理的代码统一管理了。
动态代理模式的问题:它会拦截的被代理类对象的方法,无法通过规范来拦截指定的方法。
1.3. Spring中的AOP
通过动态代理模式的实现后,我们可以定义AOP其实就是用于通过规则设置来拦截方法,加入可以统一处理的代码。
1.3.1. 关于代理的选择
在spring中,框架会根据目标类是否实现了接口来决定采用哪种动态代理的方式。
1.3.2. AOP相关术语
Joinpoint(连接点):
所谓连接点是指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点。
---就是根据规则,可以指定拦截的方法,我们将每一个被拦截的方法称为连接点。
Pointcut(切入点):
--所谓的切入点,就是拦截方法设置的规则
所谓切入点是指我们要对哪些Joinpoint进行拦截的定义。
Advice(通知/增强):
--就是可以设置在方法之前拦截或者方法执行之后拦截或者方法出异常后拦截,或者方法之前和之后都拦截。我们将这些拦截场景称为通知
所谓通知是指拦截到Joinpoint之后所要做的事情就是通知。
通知的类型:前置通知,后置通知,异常通知,最终通知,环绕通知。
Aspect(切面):
--所谓的切面就是我们的拦截处理类。
是切入点和通知(引介)的结合。
2. 基于XML配置AOP
通过XML的方式配置AOP
2.1. 搭建环境
2.1.1. 第一步:创建一个Java项目
需求:编写一个切面类,在执行login,register,undo方法,分别在方法执行前、方法之后、异常出现后、分别方法执行前后调用编写统一处理的代码,
创建一个Java项目,加入Spring框架的基础支持包
2.1.2. 第二步:编写业务层类和接口
--StudentService接口
package com.sxt.service; public interface StudentService { /** * 学生注册 */ void reigster(); /** * 学生登录 */ void login(); /** * 学生注销 */ void undo(); } |
--业务层StudentServiceImpl实现类
package com.sxt.service.impl; import com.sxt.service.StudentService; public class SutdentServiceImpl implements StudentService { @Override public void reigster() { System.out.println("-学生注册-"); } @Override public void login() { System.out.println("-学生登录-"); } @Override public void undo() { System.out.println("-学生注销-"); } } |
2.1.3. 第三步:编写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:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd">
<bean name="studentService" class="com.sxt.service.impl.SutdentServiceImpl"> </bean> </beans> |
2.1.4. 第四步:编写测试代码
package com.sxt.test; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import com.sxt.service.StudentService; //指定Junit之前测试之前,加入Spring框架启动的逻辑代码 @RunWith(SpringJUnit4ClassRunner.class) //指定Spring启动必须的配置文件 @ContextConfiguration(locations="classpath:bean.xml") public class StudentServiceTest { //获得Spring容器中的对象 @Autowired private StudentService studentService; //直接调用@Autowired获得容器对象 @Test public void reigster() { studentService.reigster(); } //直接调用@Autowired获得容器对象 @Test public void login() { studentService.login(); } } |
2.2. 配置AOP
2.2.1. 第一步:加入AOP的支持包
--注意:必须要导入加入支持AOP的包。
Spring的AOP包基于AspectJ框架,所以必须加入AspectJ-->aspectjweaver.jar
AspectJ下载路径:http://www.eclipse.org/aspectj/downloads.php
2.2.2. 第二步:编写一个切面类
package com.sxt.aop; import org.aspectj.lang.ProceedingJoinPoint; public class Aop { public void before() { System.out.println("-before-方法执行之前-"); } public void after() { System.out.println("-after-方法执行之后-"); } //around需要一个ProceedingJoinPoint对象指定方法调用之前后之后 public void around(ProceedingJoinPoint point) { System.out.println("-around-在方法之前后执行-"); try { //调用拦截方法的逻辑代码 point.proceed(); } catch (Throwable e) { e.printStackTrace(); } System.out.println("-around-在方法之后执行-"); } public void afterThrowing() { System.out.println("-afterThrowing-出了异常后执行"); } public void afterReturning() { System.out.println("-afterReturning-方法执行成功之后-"); } } |
2.2.3. 第三步:配置AOP配置
<?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:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd"> <bean name="now" class="java.util.Date"></bean> <bean name="studentService" class="com.sxt.service.impl.SutdentServiceImpl"> </bean> <!-- aop配置 --> <!-- 创建AOP的对象到容器 --> <bean name="aop" class="com.sxt.aop.Aop"></bean> <!-- aop:config: 用于设置AOP拦截的跟标签 --> <aop:config > <!--aop:aspect标签 指定AOP的切面,以及拦截规则对应的方法 --> <aop:aspect id="aop-aspect" ref="aop"> <!-- 拦截的方法必须不出异常,才在方法执行之后加入切面逻辑代码 --> <aop:after-returning method="afterReturning" pointcut="execution(* com.sxt.service.impl.SutdentServiceImpl.*())"/> <!-- 拦截的方法必须出了异常,才在方法执行之后加入切面逻辑代码 --> <aop:after-throwing method="afterThrowing" pointcut="execution(* com.sxt.service.impl.SutdentServiceImpl.*())"/> <!-- 拦截的方法不管有没有异常,可以实现在拦截方法之前和之后分别加入不同的逻辑代码 --> <aop:around method="around" pointcut="execution(* com.sxt.service.impl.SutdentServiceImpl.*())" /> <!-- 拦截的方法不管有没有异常,可以实现在拦截方法之前加入切面的逻辑代码 --> <aop:before method="before" pointcut="execution(* com.sxt.service.impl.SutdentServiceImpl.*())"/> <!-- 拦截的方法不管有没有异常,可以实现在拦截方法之后加入切面的逻辑代码 --> <aop:after method="after" pointcut="execution(* com.sxt.service.impl.SutdentServiceImpl.*())"/> </aop:aspect> </aop:config> </beans> |
2.3. 切入点表达式说明
execution:
匹配方法的执行(常用) execution(表达式) 表达式语法:execution([修饰符] 返回值类型 包名.类名.方法名(参数)) 写法说明: 全匹配方式: public void com.itheima.service.impl.CustomerServiceImpl.saveCustomer() 访问修饰符可以省略 void com.itheima.service.impl.CustomerServiceImpl.saveCustomer() 返回值可以使用*号,表示任意返回值 * com.itheima.service.impl.CustomerServiceImpl.saveCustomer() 包名可以使用*号,表示任意包,但是有几级包,需要写几个* * *.*.*.*.CustomerServiceImpl.saveCustomer() 使用..来表示当前包,及其子包 * com..CustomerServiceImpl.saveCustomer() 类名可以使用*号,表示任意类 * com..*.saveCustomer() 方法名可以使用*号,表示任意方法 * com..*.*() 参数列表可以使用*,表示参数可以是任意数据类型,但是必须有参数 * com..*.*(*) 参数列表可以使用..表示有无参数均可,有参数可以是任意类型 * com..*.*(..) 全通配方式: * *..*.*(..) |
*:通配的是任何的字符串
.. :如果通配包的时候,就是是包以及它子包的类,如果通配是方法参数,就是不限定任何参数
2.4. 常用标签
2.4.1. <aop:config>
作用:
用于声明开始aop的配置
2.4.2. <aop:aspect>
作用:
用于配置切面。
属性:
id:给切面提供一个唯一标识。
ref:引用配置好的通知类bean的id。
2.4.3. <aop:pointcut>
作用:
用于配置切入点表达式
属性:
expression:用于定义切入点表达式。
id:用于给切入点表达式提供一个唯一标识。
2.4.4. <aop:before>
作用:
用于配置前置通知
属性:
method:指定通知中方法的名称。
pointct:定义切入点表达式
pointcut-ref:指定切入点表达式的引用
2.4.5. <aop:after-returning>
作用:
用于配置后置通知
属性:
method:指定通知中方法的名称。
pointct:定义切入点表达式
pointcut-ref:指定切入点表达式的引用
2.4.6. <aop:after-throwing>
作用:
用于配置异常通知
属性:
method:指定通知中方法的名称。
pointct:定义切入点表达式
pointcut-ref:指定切入点表达式的引用
2.4.7. <aop:after>
作用:
用于配置最终通知
属性:
method:指定通知中方法的名称。
pointct:定义切入点表达式
pointcut-ref:指定切入点表达式的引用
2.4.8. <aop:around>
作用:
用于配置环绕通知
属性:
method:指定通知中方法的名称。
pointct:定义切入点表达式
pointcut-ref:指定切入点表达式的引用
3. 基于注解配置AOP
3.1. 搭建环境
3.1.1. 第一步:创建一个Java项目
创建一个Java项目,加入Spring框架的基础支持包
3.1.2. 第二步:编写业务层类和接口
--StudentService接口
package com.sxt.service; public interface StudentService { /** * 学生注册 */ void reigster(); /** * 学生登录 */ void login(); /** * 学生注销 */ void undo(); } |
--业务层StudentServiceImpl实现类
package com.sxt.service.impl; import com.sxt.service.StudentService; @Service public class SutdentServiceImpl implements StudentService { @Override public void reigster() { System.out.println("-学生注册-"); } @Override public void login() { System.out.println("-学生登录-"); } @Override public void undo() { System.out.println("-学生注销-"); } } |
3.1.3. 第三步:编写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:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd"> <context:component-scan base-package="com.sxt"></context:component-scan> </beans> |
--注意:要使用context命名空间下的标签,必须勾上Spring配置文件bean.xml的context命名空间。
3.1.4. 第四步:编写测试代码
package com.sxt.test; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import com.sxt.service.StudentService; //指定Junit之前测试之前,加入Spring框架启动的逻辑代码 @RunWith(SpringJUnit4ClassRunner.class) //指定Spring启动必须的配置文件 @ContextConfiguration(locations="classpath:bean.xml") public class StudentServiceTest { //获得Spring容器中的对象 @Autowired private StudentService studentService; //直接调用@Autowired获得容器对象 @Test public void reigster() { studentService.reigster(); } //直接调用@Autowired获得容器对象 @Test public void login() { studentService.login(); } } |
3.2. 配置AOP
3.2.1. 第一步:加入AOP的支持包
--注意:必须要导入加入支持AOP的包。
Spring的AOP包基于AspectJ框架,所以必须加入AspectJ-->aspectjweaver.jar
AspectJ下载路径:http://www.eclipse.org/aspectj/downloads.php
3.2.2. 第二步:编写一个切面类
package com.sxt.aop; 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.springframework.stereotype.Component; //必须使用组件注解将切面类的对象创建放在Spring容器 @Component //必须声明该类为一个切面类 @Aspect public class Aop { @Before(value = "execution(* com.sxt.service.impl.SutdentServiceImpl.*(..))") public void before() { System.out.println("-before-方法执行之前-"); } @After(value = "execution(* com.sxt.service.impl.SutdentServiceImpl.*(..))") public void after() { System.out.println("-after-方法执行之后-"); } @Around(value = "execution(* com.sxt.service.impl.SutdentServiceImpl.*(..))") public void around(ProceedingJoinPoint point) { System.out.println("-around-在方法之前后执行-"); try { point.proceed(); } catch (Throwable e) { e.printStackTrace(); } System.out.println("-around-在方法之后执行-"); } @AfterThrowing(value="execution(* com.sxt.service.impl.SutdentServiceImpl.*(..))") public void afterThrowing() { System.out.println("-afterThrowing-出了异常后执行"); } @AfterReturning(value="execution(* com.sxt.service.impl.SutdentServiceImpl.*(..))") public void afterReturning() { System.out.println("-afterReturning-方法执行成功之后-"); } } |
3.2.3. 第三步:配置AOP配置
<?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:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd"> <context:component-scan base-package="com.sxt"></context:component-scan> <!-- 使用注解配置AOP配置配置自动注入AOP --> <aop:aspectj-autoproxy></aop:aspectj-autoproxy> </beans> |
3.3. 常用注解
3.3.1. @Aspect
作用:
把当前类声明为切面类。
3.3.2. @Before
作用:
把当前方法看成是前置通知。
属性:
value:用于指定切入点表达式,还可以指定切入点表达式的引用。
3.3.3. @AfterReturning
作用:
把当前方法看成是后置通知。
属性:
value:用于指定切入点表达式,还可以指定切入点表达式的引用。
3.3.4. @AfterThrowing
作用:
把当前方法看成是异常通知。
属性:
value:用于指定切入点表达式,还可以指定切入点表达式的引用。
3.3.5. @After
作用:
把当前方法看成是最终通知。
属性:
value:用于指定切入点表达式,还可以指定切入点表达式的引用。
3.3.6. @Around
作用:
把当前方法看成是环绕通知。
属性:
value:用于指定切入点表达式,还可以指定切入点表达式的引用。
3.3.7. @Pointcut
作用:
指定切入点表达式
属性:
value:指定表达式的内容
3.4. 不使用XML的配置方式
使用配置类来替换配置文件
@Configuration @ComponentScan(basePackages="com.itheima") @EnableAspectJAutoProxy public class SpringConfiguration { } |
4. Spring的JdbcTemplate
4.1. JdbcTemplate概述
Spring JDBC是Sring框架的持久层子框架。用于对数据库的操作。
而JdbcTemplate它是spring jdbc子框架中提供的一个操作类,用于对原始Jdbc API对象的简单封装。
我们今天的主角在spring-jdbc-4.24.RELEASE.jar中,我们在导包的时候,除了要导入这个jar包外,还需要导入一个spring-tx-4.2.4.RELEASE.jar(它是和事务相关的)。
JdbcTemplate类所在目录:
4.2. JdbcTemplate的创建
我们如何创建一个JdbcTemplate对象呢?不妨,我们通过简单分析一下JdbcTemplate的源码,从而理解创建一个JdbcTemplate对象需要声明前提条件。
源码如下:
public JdbcTemplate() { } /** * Construct a new JdbcTemplate, given a DataSource to obtain connections from. * <p>Note: This will not trigger initialization of the exception translator. * @param dataSource the JDBC DataSource to obtain connections from */ public JdbcTemplate(DataSource dataSource) { setDataSource(dataSource); afterPropertiesSet(); } /** * Construct a new JdbcTemplate, given a DataSource to obtain connections from. * <p>Note: Depending on the "lazyInit" flag, initialization of the exception translator * will be triggered. * @param dataSource the JDBC DataSource to obtain connections from * @param lazyInit whether to lazily initialize the SQLExceptionTranslator */ public JdbcTemplate(DataSource dataSource, boolean lazyInit) { setDataSource(dataSource); setLazyInit(lazyInit); afterPropertiesSet(); } |
通过这段代码我们可以理解(注意源码标红处),创建一个JdbcTemplate对象,需要一个获得数据库连接的的数据源。
所以要创建JdbcTemplate对象,必须学会在Spring里面配置数据源。
4.3. Spring配置数据源
4.4. 环境搭建
4.4.1. 第一步:导入包
4.4.2. 配置文件准备
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd">
</beans> |
4.4.3. 配置数据源
我们之前已经接触过了两个数据源,一个是C3P0,一个是DBCP。要想使用这两数据源都需要导入对应的jar包。
4.4.3.1. 配置C3P0数据源
导入到工程的lib目录。在spring的配置文件中配置:
<!-- 配置数据源 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="com.mysql.jdbc.Driver"></property> <property name="jdbcUrl" value="jdbc:mysql:///spring_day04"></property> <property name="user" value="root"></property> <property name="password" value="1234"></property> </bean> |
4.4.3.2. 配置DBCP数据源
导入到工程的lib目录。在spring的配置文件中配置:
<!-- 配置数据源 --> <bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"></property> <property name="url" value="jdbc:mysql:// /spring_day04"></property> <property name="username" value="root"></property> <property name="password" value="1234"></property> </bean> |
4.4.3.3. 配置spring内置数据源
spring框架也提供了一个内置数据源,我们也可以使用spring的内置数据源,它就在spring-jdbc-4.2.4.REEASE.jar包中:
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"></property> <property name="url" value="jdbc:mysql:///spring_day04"></property> <property name="username" value="root"></property> <property name="password" value="1234"></property> </bean> |
4.4.4. 将数据库连接的信息配置到属性文件中:
【定义属性文件jdbc.properties】
jdbc.driverClass=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql:///spring_day02 jdbc.username=root jdbc.password=123 |
【引入外部的属性文件】
一种方式: <!-- 引入外部属性文件: --> <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="location" value="classpath:jdbc.properties"/> </bean> 二种方式: <context:property-placeholder location="classpath:jdbc.properties"/> |
4.5. JdbcTemplate的增删改查操作
注意:测试要导入spring-test-4.2.9.RELEASE.jar包
--配置文件
<?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-4.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd"> <!-- 配置数据源 --> <bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"></property> <property name="url" value="jdbc:mysql://localhost:3306/sms"></property> <property name="username" value="root"></property> <property name="password" value="123456"></property> </bean> <!-- 配置JdbcTemplate --> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <!-- 配置指定连接池 --> <property name="dataSource" ref="dataSource"></property> </bean> </beans> |
--实体类
package com.sxt.entity; public class Student { private Long stuId;//BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '学生编号', private String stuName;//VARCHAR(50) NULL DEFAULT NULL COMMENT '学生名字', private String stuAge;//INT(11) NULL DEFAULT NULL COMMENT '学生年龄', private String stuPassword;//VARCHAR(50) NULL DEFAULT NULL COMMENT '登录密码', public Long getStuId() { return stuId; } public void setStuId(Long stuId) { this.stuId = stuId; } public String getStuName() { return stuName; } public void setStuName(String stuName) { this.stuName = stuName; } public String getStuAge() { return stuAge; } public void setStuAge(String stuAge) { this.stuAge = stuAge; } public String getStuPassword() { return stuPassword; } public void setStuPassword(String stuPassword) { this.stuPassword = stuPassword; } } |
--实现代码
package com.sxt.test; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import java.util.Map; import javax.sql.DataSource; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataAccessException; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.ResultSetExtractor; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import com.sxt.entity.Student; @RunWith(value = SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = "classpath:bean.xml") public class JdbcTemplateTest { @Autowired private DataSource datasource; @Autowired private JdbcTemplate jdbctemplate; /** * 测试数据源是否成功 */ @Test public void dataSource() { try { System.out.println(datasource.getConnection()); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } /** * 测试jdbctemplate对象是否成功 */ @Test public void jdbctemplate() { System.out.println(jdbctemplate); } /** * 测试jdbctemplate插入数据 */ @Test public void insert() { jdbctemplate.update("INSERT INTO tb_student (stu_name, stu_age, stu_password) VALUES (?, ?, ?)", new Object[] {"o1",30,"999"}); } /** * 测试jdbctemplate删除数据 */ @Test public void delete() { jdbctemplate.update("DELETE FROM tb_student WHERE stu_id=?", 15); } /** * 测试jdbctemplate更新数据 */ @Test public void update() { jdbctemplate.update("UPDATE tb_student SET stu_name=? WHERE stu_id=?", new Object[] {"测试",16}); } /** * 测试jdbctemplate查询所有数据返回List */ @Test public void findAll() { List<Map<String, Object>> students = jdbctemplate.queryForList("SELECT * FROM tb_student"); for (Map<String, Object> student : students) { System.out.println("学生名:" + student.get("stu_name")); } } /** * 测试jdbctemplate查询返回一个Map */ @Test public void getByMap() { Map<String, Object> entity = jdbctemplate.queryForMap("SELECT * FROM tb_student WHERE stu_id=?",1); System.out.println(entity.get("stu_name")); } /** * 测试jdbctemplate查询返回一个JavaBean */ @Test public void getByEntity() { ResultSetExtractor<Student> rowMapper = new ResultSetExtractor<Student>() { @Override public Student extractData(ResultSet rs) throws SQLException, DataAccessException { Student student=new Student(); if(rs.next()) { student.setStuName(rs.getString("stu_name")); } return student; } }; Student student = jdbctemplate.query("SELECT * FROM tb_student WHERE stu_id=?",new Object[] {1}, rowMapper); System.out.println(student.getStuName()); } /** * 测试测试jdbctemplate查询返回一个JavaBean元素的List */ @Test public void getByEntityList() { ResultSetExtractor<List<Student>> rowMapper = new ResultSetExtractor<List<Student>>() { @Override public List<Student> extractData(ResultSet rs) throws SQLException, DataAccessException { List<Student> students =new ArrayList<Student>(); while(rs.next()) { Student student=new Student(); student.setStuId(rs.getLong("stu_id")); student.setStuName(rs.getString("stu_name")); student.setStuPassword(rs.getString("stu_password")); students.add(student); } return students; } }; List<Student> query = jdbctemplate.query("SELECT * FROM tb_student", rowMapper); for (Student student : query) { System.out.println(student.getStuName()); } } } |
5. Spring中的事务控制
5.1. Spring事务控制我们要明确的
第一:
JavaEE体系进行分层开发,事务处理位于业务层,Spring提供了分层设计业务层的事务处理解决方案。
第二:
spring框架为我们提供了一组事务控制的接口。具体在后面的第二小节介绍。这组接口是在spring-tx-4.2.4.RELEASE.jar中。
第三:
spring的事务控制都是基于AOP的,它既可以使用编程的方式实现,也可以使用配置的方式实现。我们学习的重点是使用配置的方式实现。
5.2. 事务处理是什么
在操作数据库时(增删改),如果同时操作多次数据,我们从业务希望,要不全部成功,要不全部失败。这种情况称为事务处理。
A君转账给B君。
第一步,扣除A君账号要转的金额
第二步,增加B君账号的金额
5.3. Spring中事务控制的API介绍
5.3.1. PlatformTransactionManager
Spring所有事务代理类都是基于PlatformTransactionManager接口的实现。
此接口是spring的事务管理器,它里面提供了我们常用的操作事务的方法,如下代码片段:
PlatformTransactionManager包括以下三个操作
//获得事务信息 TransactionStatus getTransaction(TransactionDefinition definition) //提交事务 void commit(TransactionStatus status) //回滚事务 void rollback(TransactionStatus status) |
我们在开发中都是使用它的实现类,如下:
--用于Spring JDBC以及Mybatis框架的事务代理 DataSourceTransactionManager --用于Hibernate框架事务代理 HibernateTransactionManager --用于Jpa框架的事务代理 JpaTransactionManager --用于JDO框架的事务代码 JdoTransactionManager --用于Jta事务代理,一个事务跨多资源必须要使用 JtaTransactionManager |
5.3.2. TransactionDefinition
5.3.2.1. 说明
该接口注意定义了:事务的传播行为,事务的隔离级别,获得事务信息的方法。所以在配置事务的传播行为,事务的隔离级别已经需要获得事务信息时,可以通过查阅该类的代码获得相关信息。
TransactionDefinition源码如下:
public interface TransactionDefinition { //事务的传播行为 int PROPAGATION_REQUIRED = 0; int PROPAGATION_SUPPORTS = 1; int PROPAGATION_MANDATORY = 2; int PROPAGATION_REQUIRES_NEW = 3; int PROPAGATION_NOT_SUPPORTED = 4; int PROPAGATION_NEVER = 5; int PROPAGATION_NESTED = 6; //事务的隔离级别 int ISOLATION_DEFAULT = -1; int ISOLATION_READ_UNCOMMITTED = Connection.TRANSACTION_READ_UNCOMMITTED; int ISOLATION_READ_COMMITTED = Connection.TRANSACTION_READ_COMMITTED; int ISOLATION_REPEATABLE_READ = Connection.TRANSACTION_REPEATABLE_READ; int ISOLATION_SERIALIZABLE = Connection.TRANSACTION_SERIALIZABLE; int TIMEOUT_DEFAULT = -1; //获得事务信息 int getPropagationBehavior(); int getIsolationLevel(); int getTimeout(); boolean isReadOnly(); String getName(); } |
5.3.2.2. 事务的传播行为
REQUIRED:如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。一般的选择(默认值)99%
SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行(没有事务)
MANDATORY:使用当前的事务,如果当前没有事务,就抛出异常
REQUERS_NEW:新建事务,如果当前在事务中,把当前事务挂起。
//查询的时候配置
NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起
//查询的时候配置
NEVER:以非事务方式运行,如果当前存在事务,抛出异常
NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行REQUIRED类似的操作。
5.3.2.3. 事务隔离级别
所谓的事务隔离级别就是,同一个数据库出现多个不同的线程操作(事务)。每个事务之间的关系就是事务隔离级别。
ISOLATION_DEFAULT:默认隔离级别,由数据库本身决定以下四种中的某一种。
根据现实情况,事务隔离级别有四个。
根据四个隔离级别,可能会出现,脏读,不可重复读,幻读
ISOLATION_READ_UNCOMMITTED:可以读未另一个事务提交的数据
(一个事务操作时,另一个事务可以查询,也可以提交,还可以读取别的事务没有提交的数据)
ISOLATION_READ_COMMITTED :只能读已提交的数据,(解决脏读问题,ORACLE默认)
(一个事务操作时,另一个事务可以查询,也可以提交,但是不能读取别的是没有提交的数据)
ISOLATION_REPEATABLE_READ:是否读取其他事务提交修改后的数据,解决不可以重复读问题(MySQL默认)(在一个事务操作时,另外一个事务不能提交,但是可以查询)
ISOLATION_SERIALIZABLE:是否读取其他提交增加后的数据,解决幻读问题(在一个事务操作时,例外一个事务不能提交,也不能查询)
脏读,如图所示
不可重复图,如图所示
幻读,如图所示
5.4. 基于xml实现事务代理(声明式事务)
5.4.1. 第一步:创建项目带入包
注意事项:事务代理依赖AOP实现包
spring-aop-4.2.9.RELEASE.jar
aopalliance-1.0.jar
spring-aspects-4.2.9.RELEASE.jar
aspectjweaver.jar
5.4.2. 第二步:编写代码
包括以下代码
--Student实体类
package com.sxt.entity; public class Student { private Long stuId;//BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '学生编号', private String stuName;//VARCHAR(50) NULL DEFAULT NULL COMMENT '学生名字', private Integer stuAge;//INT(11) NULL DEFAULT NULL COMMENT '学生年龄', private String stuPassword;//VARCHAR(50) NULL DEFAULT NULL COMMENT '登录密码', public Long getStuId() { return stuId; } public void setStuId(Long stuId) { this.stuId = stuId; } public String getStuName() { return stuName; } public void setStuName(String stuName) { this.stuName = stuName; } public Integer getStuAge() { return stuAge; } public void setStuAge(Integer stuAge) { this.stuAge = stuAge; } public String getStuPassword() { return stuPassword; } public void setStuPassword(String stuPassword) { this.stuPassword = stuPassword; } } |
--DAO接口
package com.sxt.dao; /** * 编写一个DAO接口 * @author ranger * * @param <T> */ public interface DAO<T> { /** * 插入数据 * @param entity * @return */ int insert(T entity); } |
--StudentDAO类
package com.sxt.dao; import org.springframework.jdbc.core.JdbcTemplate; import com.sxt.entity.Student; /** * 使用Spring JDBC子框架操作数据库 * @author ranger * */ public class StudentDAO implements DAO<Student> { private JdbcTemplate jdbcTemplate; public void setJdbcTemplate(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } @Override public int insert(Student entity) { int count = jdbcTemplate.update("INSERT INTO tb_student (stu_name, stu_age, stu_password) VALUES (?, ?, ?)", new Object[] {entity.getStuName(),entity.getStuAge(),entity.getStuPassword()}); return count; } } |
--StudentService类
package com.sxt.service; import com.sxt.dao.StudentDAO; import com.sxt.entity.Student; public class StudentService { private StudentDAO studentDAO; public void setStudentDAO(StudentDAO studentDAO) { this.studentDAO = studentDAO; } /** * 通过配置了事务代理,要不全部成功要不全部失败 * 注意:事务代理是通过处理异常抛出来实现事务代理的,所以必须不能在该方法捕捉异常 * 如果捕捉了异常,事务代理失效。 * @param entity * @return */ public void save(Student entity){ //通过插入两条数据,到时将数据库的stuName设置为唯一的来判断事务是否生效 studentDAO.insert(entity); studentDAO.insert(entity); } } |
--StudentAction类
package com.sxt.action; import com.sxt.entity.Student; import com.sxt.service.StudentService; public class StudentAction { private StudentService studentService; public void setStudentService(StudentService studentService) { this.studentService = studentService; } public void save() { //封装数据 Student entity=new Student(); entity.setStuName("zhangsan"); entity.setStuAge(21); entity.setStuPassword("123"); studentService.save(entity); } } |
5.4.3. 第三步:配置文件
<?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" 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-4.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.2.xsd"> <!-- 配置数据源 --> <bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"></property> <property name="url" value="jdbc:mysql://localhost:3306/sms"></property> <property name="username" value="root"></property> <property name="password" value="123456"></property> </bean> <!-- 配置JdbcTemplate --> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <!-- 配置指定连接池 --> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 事务代理 --> <bean id="tm" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 配置事务代理规则 --> <tx:advice id="txAdvice" transaction-manager="tm"> <tx:attributes> <!-- save方法,事务拦截 --> <tx:method name="save*" propagation="REQUIRED" isolation="DEFAULT" /> <tx:method name="update*" propagation="REQUIRED" isolation="DEFAULT" /> <tx:method name="delete*" propagation="REQUIRED" isolation="DEFAULT" /> <!-- 查询方法使用只读事务 --> <tx:method name="find*" read-only="true"/> <tx:method name="get*" read-only="true"/>
</tx:attributes> </tx:advice> <!-- AOP拦截事务 --> <aop:config> <aop:pointcut expression="execution(* com.sxt.service.StudentService.save(..))" id="aop-pointcut"/> <aop:advisor advice-ref="txAdvice" pointcut-ref="aop-pointcut"/> </aop:config> <bean id="studentDAO" class="com.sxt.dao.StudentDAO"> <property name="jdbcTemplate" ref="jdbcTemplate"></property> </bean> <bean id="studentService" class="com.sxt.service.StudentService"> <property name="studentDAO" ref="studentDAO"></property> </bean> <bean id="studentAction" class="com.sxt.action.StudentAction"> <property name="studentService" ref="studentService"></property> </bean> </beans> |
5.4.4. 第四步:测试代码
package com.sxt.test; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import com.sxt.action.StudentAction; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations="classpath:bean.xml") public class StudentActionTest { @Autowired private StudentAction studentAction; @Test public void save() { studentAction.save(); } } |
5.4.5. 第五步:数据库stu_name修改为唯一的
--结果,如果插入的两条数据,要不全部插入,要不全部失败。表示事务配置成功!!
5.5. 基于注解实现事务代理(编程式事务)
5.5.1. 第一步:创建项目带入包
注意事项:事务代理依赖AOP实现包
spring-aop-4.2.9.RELEASE.jar
aopalliance-1.0.jar
spring-aspects-4.2.9.RELEASE.jar
aspectjweaver.jar
5.5.2. 第二步:编写代码
包括以下代码
--Student实体类
package com.sxt.entity; public class Student { private Long stuId;//BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '学生编号', private String stuName;//VARCHAR(50) NULL DEFAULT NULL COMMENT '学生名字', private Integer stuAge;//INT(11) NULL DEFAULT NULL COMMENT '学生年龄', private String stuPassword;//VARCHAR(50) NULL DEFAULT NULL COMMENT '登录密码', public Long getStuId() { return stuId; } public void setStuId(Long stuId) { this.stuId = stuId; } public String getStuName() { return stuName; } public void setStuName(String stuName) { this.stuName = stuName; } public Integer getStuAge() { return stuAge; } public void setStuAge(Integer stuAge) { this.stuAge = stuAge; } public String getStuPassword() { return stuPassword; } public void setStuPassword(String stuPassword) { this.stuPassword = stuPassword; } } |
--DAO接口
package com.sxt.dao; /** * 编写一个DAO接口 * @author ranger * * @param <T> */ public interface DAO<T> { /** * 插入数据 * @param entity * @return */ int insert(T entity); } |
--StudentDAO类
package com.sxt.dao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; import com.sxt.entity.Student; /** * 使用Spring JDBC子框架操作数据库 * @author ranger * */ @Repository public class StudentDAO implements DAO<Student> { @Autowired private JdbcTemplate jdbcTemplate; @Override public int insert(Student entity) { int count = jdbcTemplate.update("INSERT INTO tb_student (stu_name, stu_age, stu_password) VALUES (?, ?, ?)", new Object[] {entity.getStuName(),entity.getStuAge(),entity.getStuPassword()}); return count; } } |
--StudentService类
package com.sxt.service; 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; import com.sxt.dao.StudentDAO; import com.sxt.entity.Student; @Service public class StudentService { @Autowired private StudentDAO studentDAO; /** * 通过配置了事务代理,要不全部成功要不全部失败 * 注意:事务代理是通过处理异常抛出来实现事务代理的,所以必须不能在该方法捕捉异常 * 如果捕捉了异常,事务代理失效。 * @param entity * @return */ @Transactional(isolation=Isolation.DEFAULT,propagation=Propagation.REQUIRED) public void save(Student entity){ //通过插入两条数据,到时将数据库的stuName设置为唯一的来判断事务是否生效 studentDAO.insert(entity); studentDAO.insert(entity); } } |
--StudentAction类
package com.sxt.action; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import com.sxt.entity.Student; import com.sxt.service.StudentService; @Controller public class StudentAction { @Autowired private StudentService studentService; public void save() { Student entity=new Student(); entity.setStuName("zhangsan"); entity.setStuAge(21); entity.setStuPassword("123"); studentService.save(entity); } } |
5.5.3. 第三步:配置文件
<?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" 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-4.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.2.xsd"> <!-- 扫描注解的对象到Spring 容器 --> <context:component-scan base-package="com.sxt"></context:component-scan> <!-- 配置数据源 --> <bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"></property> <property name="url" value="jdbc:mysql://localhost:3306/sms"></property> <property name="username" value="root"></property> <property name="password" value="123456"></property> </bean> <!-- 配置JdbcTemplate --> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <!-- 配置指定连接池 --> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 事务代理 --> <bean id="tm" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 支持使用注解配置事务代理 --> <tx:annotation-driven transaction-manager="tm"/> </beans> |
5.5.4. 第四步:测试代码
package com.sxt.test; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import com.sxt.action.StudentAction; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations="classpath:bean.xml") public class StudentActionTest { @Autowired private StudentAction studentAction; @Test public void save() { studentAction.save(); } } |
5.6. 纯注解的支持
注意,如果需要纯注解,将以上基于注解的bean.xml配置文件修改为:
package com.sxt.config; import javax.sql.DataSource; import org.apache.commons.dbcp2.BasicDataSource; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.transaction.annotation.EnableTransactionManagement; @Configuration //表示启用支持注解的事务代理 <tx:annotation-driven transaction-manager="tm"/> @EnableTransactionManagement //表示支持扫描组件 <context:component-scan base-package="com.sxt" /> @ComponentScan(basePackages="com.sxt") public class BeanConfig { /** * 获得数据库连接池 * @return */ @Bean(name="dataSource") public DataSource getDataSource() { BasicDataSource dataSource=new BasicDataSource(); dataSource.setDriverClassName("com.mysql.jdbc.Driver"); dataSource.setUrl("jdbc:mysql://localhost:3306/sms"); dataSource.setUsername("root"); dataSource.setPassword("123456"); return dataSource; } /** * 获得JdbcTemplate * @return */ @Bean(name="jdbcTemplate") public JdbcTemplate geJdbcTemplate() { JdbcTemplate jdbcTemplate=new JdbcTemplate(); //必须指定jdbcTemplate的数据源 jdbcTemplate.setDataSource(getDataSource()); return jdbcTemplate; } /** * 创建一个事务代理对象 * @return */ @Bean public DataSourceTransactionManager getDataSourceTransactionManager() { DataSourceTransactionManager tm=new DataSourceTransactionManager(); //一定要指定事务代理的数据源 tm.setDataSource(getDataSource()); return tm; } } |
--测试类修改为
package com.sxt.test; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import com.sxt.action.StudentAction; import com.sxt.config.BeanConfig; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes=BeanConfig.class) public class StudentActionTest { @Autowired private StudentAction studentAction; @Test public void save() { studentAction.save(); } } |
6. 总结
问题:遇到一些重复的代码需要统一处理,我们通过什么设计模式实现?
答:代理模式
问题:代理模式有个缺陷,一个业务类必须对应一个代理处理类。这样太麻烦了,有什么设计模式可以实现一个代理处理类代理多个业务类的方法
答:动态代理模式
问题:JDK提供的动态代理模式是通过哪些类或者接口实现的。
答:Proxy,InvocationHandler
注意基于JDK实现的动态代理模式,返回的必须是一个接口。这样导致业务类必须要有一个接口。
问题:代理模式或者动态代理模式,必须要三个要素。
- 原对象 (源)
- 代理对象 (目标)
- 代理的业务处理对象 (处理过程)
问题:Spring AOP是什么
答:面向切面的编程。说白就是做了动态代理的事情。只是比起动态代理模式的实现,更加精细。
连接点:就是每个被拦截(代理)的方法,都是一个连接点。
切点:就是设置拦截方法的规则的配置
通知:AOP拦截的情况,方法执行之前,方法执行之后,方法执行的前后,方法出异常之后...
切面:就是设置拦截处理的业务逻辑。(处理过程)
问题:AOP有什么作用
答:就是和动态代理模式的实现一样,可以统一处理通用的代码,如:事务代理,日志统一处理。
Spring JDBC持久层子框架。
Spring JDBC的作用:就是对数据库增删改查!!!!
我们只要理解JdbcTemplate就可以满足开发的需要!!
事务代理:所谓的事务代理就是通过Spring提供的AOP机制,实现统一的处理数据库事务。
事务代理基于AOP,所以实现是代理必须要导入AOP的依赖包。
声明式事务代理(XML配置)
在JdbcTemplate配置成功的基础上,对JdbcTemplate事务代理有三个步骤
- 创建一个事务代理对象 DataSourceTransactionManager对象。必须要指定代理的数据源
<!-- 第一步:配置事务代理类对象 --> <bean name="tm" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!-- 必须要指定事务代理的数据源 --> <property name="dataSource" ref="dataSource"></property> </bean> |
- 设置代理的规则
<tx:advice id="advice" transaction-manager="tm"> <!--tx:attributes 用于存放拦截规则 --> <tx:attributes> <!-- 表示insert开头的方法都要事务处理 --> <!-- tx:method:设置方法的拦截器规则 name:设置拦截器的方法,支持*通配符,如insert*,表示insert开头的都拦截事务处理 注意事项:如果方法指定了抛出的异常和Spring默认的捕捉的异常不一致,必须要指定拦截的异常 rollback-for:指定拦截的异常 isolation:设置事务的隔离级别 propagation:设置事务的传播行为 --> <tx:method name="insert*" isolation="DEFAULT" propagation="REQUIRED" rollback-for="java.lang.Exception" /> <!-- 配置find开头的方法不需要事务,是查询的方法 --> <tx:method name="find*" read-only="true" propagation="NOT_SUPPORTED"/> </tx:attributes>
</tx:advice> |
- 使用AOP拦截启动事务代理
<!-- 第三步:将规则交个AOP拦截 --> <aop:config > <!-- 设置AOP的切入点 --> <aop:pointcut expression="execution(* cn.gzsxt.service..*.*(..) )" id="pointcut"/> <aop:advisor advice-ref="advice" pointcut-ref="pointcut" /> </aop:config> |
注意事项:事务代理的业务方法,不能捕捉异常。
编程式事务(使用注解配置)
第一步:创建一个事务代理对象
第二步:启动事务支持注解的事务代理
第三步:事务注解@Transaction配置事务处理规则
7. 源码包文件夹
--Eclipse在编译后,会将所有的源码包文件夹合并,所有代码放在源码包的任何目录是没有区别的。
问题:既然编译会合并,为什么会有源码包文件夹呢?
答:其实源码包文件的作用就是代码分类的!!!!