1.IOC容器
控制反转:把对象的创建和对象之间的调用过程都交给Spring进行管理,目的是为了降低耦合度;
IOC思想基于IOC容器完成,IOC容器底层就是对象工厂
建议放到容器中的对象:dao类,service类,controller类,工具类,spring容器中的对象都是单例,容器中每个对象只存在一个;
不建议放到容器中的对象:
- 实体类对象,实体类对象的数据来自数据库,应该是在程序运行过程中创建
- servlet,filter,listener,这些类应该由tomcat负责创建与调用
将对象放到容器中有两种方式
- 在xml配置中添加bean标签
- 使用注解
2.Bean标签创建容器对象
通过容器使用对象:
- 在resources目录下配置mybeans.xml
<?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.xsd">
<!-- beans是根标签,spring把java对象称为bean
spring-beans.xsd是一种约束文件和mybatis的.dtd约束一样
-->
<!-- 告诉Spring创建对象,声明bean,
id:对象的自定义名称,唯一值,spring通过这个名称找到对象
class:类的全限定名称
spring是把创建好的对象放到map中 springMap.put("WarMovie",new WarMovieInterImpl())
一个bean标签对应一个对象
-->
<bean id="WarMovie" class="com.ywxk.spring5.service.impl.WarMovieInterImpl"></bean>
<!-- 创建一个非自定义类 -->
<bean id="myDate" class="java.util.Date"></bean>
</beans>
- 创建spring容器,通过容器获取对象
//从类路径target/classes下加载xml文件,创建容器,ApplicationContext就是spring容器
//在创建完spring容器后,spring会创建配置文件中的所有对象,默认调用的是类的无参构造
ApplicationContext context=new ClassPathXmlApplicationContext("beans.xml");
//从容器中获取某个类
WarMovieInterImpl warMovie =context.getBean("WarMovie",WarMovieInterImpl.class);
warMovie.seeMovie();
//获取非自定义类
Date date=(Date)context.getBean("myDate");
3.依赖注入(Dependency Injection)DI
bean实例在调用无参数构造方法创建对象后,就要对bean对象的属性进行初始化,初始化是由容器完成的,称为依赖注入,根据注入的方式分为set注入,构造注入;
set注入(设值注入):spring调用类的set方法,在set方法可以实现属性的赋值,实际开发中80%都是使用该方式;
//简单类型
<bean id="director" class="com.ywxk.spring5.service.T1.Director">
<!-- 为类注入属性,spring会调用对象的setXXX方法 -->
<property name="name" value="路阳"></property>
<property name="age" value="35"></property>
</bean>
//引用类型,property里面用ref="beanid"
<bean id="company" class="com.ywxk.spring5.service.T1.Company">
<property name="addr" value="北京市"></property>
<property name="companyName" value="北京文化"></property>
<property name="registerMoney" value="2000000"></property>
</bean>
<bean id="director" class="com.ywxk.spring5.service.T1.Director">
<!-- 为类注入属性,spring会调用对象的setXXX方法 -->
<property name="name" value="路阳"></property>
<property name="age" value="35"></property>
<property name="nick" value="烧水水"></property>
<property name="com" ref="company"></property>
</bean>
构造注入:spring调用类的有参构造方法,创建对象,在构造方法中完成属性的赋值;
<bean class="com.ywxk.spring5.service.T1.Player" id="player">
<!-- 一个<constructor-arg>表示一个构造方法的一个参数,
name:构造方法的形参名或者index:构造方法参数的位置,从0开始
value:构造方法形参的值
ref:当构造方法的形参类型是引用类型,使用ref
也可以省略name跟index,但是value的顺序必须是按照形参的顺序
-->
<constructor-arg name="pName" value="刘德华"></constructor-arg>
<constructor-arg index="1" value="53"></constructor-arg>
<constructor-arg name="sex" value="true"></constructor-arg>
<constructor-arg name="com" ref="company"></constructor-arg>
</bean>
public Player(String pName, int age, boolean sex,Company com) {
this.pName = pName;
this.age = age;
this.sex = sex;
this.com= com;
}
用构造注入获取file对象
<bean name="myFile" class="java.io.File">
<constructor-arg name="parent" value="C:\Users\Administrator\Desktop\临时"></constructor-arg>
<constructor-arg name="child" value="aa.txt"></constructor-arg>
</bean>
4.引用类型的自动注入
实际开发中可能会碰到一个类里面有多个引用属性,如果逐个配置标签会导致代码冗余,所以spring提供了一种自动注入引用类型的功能,该功能只能为引用属性自动赋值;
-
byName(按名称)方式:如果java类中引用类型的属性名与bean中的类的id一致,且数据类型一致,那么spring就能对该属性进行自动注入
<bean id="WarMovie" class="com.ywxk.spring5.service.impl.WarMovieInterImpl" autowire="byName"></bean>
public class WarMovieInterImpl implements MovieInter { private Player player; private Director director; ... }
<bean id="director" class="com.ywxk.spring5.service.T1.Director"> <property name="name" value="路阳"></property> <property name="age" value="35"></property> <property name="nick" value="烧水水"></property> <property name="com" ref="company"></property> </bean> <bean id="player" class="com.ywxk.spring5.service.T1.Player" > <constructor-arg value="刘德华"></constructor-arg> <constructor-arg index="1" value="53"></constructor-arg> <constructor-arg value="true"></constructor-arg> <constructor-arg ref="company"></constructor-arg> </bean>
-
byType(按类型)方式:如果java类中引用类型的属性类型与bean中的类的class是同源的,那么spring就能对该属性进行自动注入
这里的同源指的是:
- java类中引用类型的属性类型与bean中的类的class同一个类;
- java类中引用类型的属性类型是bean中的类的class的父类;
- java类中引用类型的属性类型与bean中的类的class是接口与实现类的关系;
<bean id="WarMovie" class="com.ywxk.spring5.service.impl.WarMovieInterImpl" autowire="byType"></bean>
<bean id="playerSon" class="com.ywxk.spring5.service.T1.PlayerSon" >
<constructor-arg value="刘德华"></constructor-arg>
<constructor-arg index="1" value="53"></constructor-arg>
<constructor-arg value="true"></constructor-arg>
<constructor-arg ref="company"></constructor-arg>
</bean>
public class WarMovieInterImpl implements MovieInter {
private Player player;
...
}
public class PlayerSon extends Player{
public PlayerSon(String pName, int age, boolean sex, Company com) {
super(pName, age, sex, com);
System.out.println("player子类");
}
}
5.多配置文件
如果项目较为庞大,可以使用多个配置文件
- 可以按功能模块,一个模块一个配置文件
- 按类的功能,数据库相关一个配置文件,事物功能一个配置文件,service功能一个配置文件…
<!-- 主配置文件,引入其他模块配置文件 -->
<import resource="classpath:package1/moviePackage1.xml"></import>
<import resource="companyPackage1.xml"></import>
<!-- 也可以用通配符方式,注意不要匹配到主配置文件,否则会造成死循环,通配符只能匹配当前目录的文件,不能匹配当前目录的子目录的文件 -->
<import resource="classpath:package1/package-*.xml"></import>
<!--companyPackage1.xml,公司模块所有bean-->
<bean id="companyPackage1" class="com.ywxk.spring5.package1.CompanyPackage1"></bean>
<!--moviePackage1.xml movie模块所有bean -->
<bean id="moviePackage1" class="com.ywxk.spring5.package1.MoviePackage1"></bean>
6.注解方式的依赖注入
6.注解方式创建容器对象与属性赋值
注解方式创建容器对象的步骤
- 在类上添加注解
/**
* @Component创建对象,等同于bean功能
* 等同于<bean id="movieP2" class="com.ywxk.spring5.package2.MovieP2" ></bean>
* value是对象的名称,相当于bean的id,在整个spring容器中值唯一
*/
@Component(value = "movieP2")
//@Component 注解如果没有指定bean的名字,默认为小写开头的类名
public class MovieP2 {
private String mName;
private int mYear;
private String mInfo;
...
}
- 在配置文件中添加组件扫描器
<!-- 声明组件扫描器,java中的组件就是类
base-package:组件所在的包名,组件扫描器会去这个包以及这个包下面的所有子包中查找注解类,再根据注解创建对象或者给属性赋值
指定多个包可以写多个component-scan标签,也可以在一个标签中用分号或者逗号分隔
或者指定一个父包也可以,因为扫描器扫的是指定包及子包下的所有类,尽量精确,否则影响程序运行效率
-->
<context:component-scan base-package="com.ywxk.spring5.package2"></context:component-scan>
<!-- <context:component-scan base-package="com.ywxk.spring5.package1"></context:component-scan>-->
<!-- <context:component-scan base-package="com.ywxk.spring5.package2;com.ywxk.spring5.package1"></context:component-scan>-->
<!-- <context:component-scan base-package="com.ywxk.spring5.package2,com.ywxk.spring5.package1"></context:component-scan>-->
<!-- <context:component-scan base-package="com.ywxk.spring5"></context:component-scan>-->
spring中和@Component功能类似的,创建对象的注解还有:
- @Repository(用在持久层的类上面):放在dao的实现类上面,表示创建dao对象,dao对象是能访问数据库的;
- @Service(用在业务层类的上面):放在service的实现类上面,创建service对象,service对象是做业务处理的,可以有事物等功能的;
- @Controller(用在控制器层的上面):放在controller类的上面,创建控制器对象的,控制器对象能够接受用户提交的数据,显示请求的处理结果;
以上3个注解的使用语法、功能和@Component一样,但是他们有额外的含义,是给项目对象分层的;
当一个类同时不属于持久层、业务层、控制器层的时候,这个类可以用@Component注解
注解方式给属性赋值
可以省略set方法
@Component
public class PlayerP2 {
//在属性上方加@Value,推荐使用
@Value("黄渤")
private String pName;
@Value("23")
private int pAge;
@Value("ggg")
private String pNick;
/**
* @Autowired是给引用类型赋值的注解,使用的是自动注入原理,有byName与byType两种方式
* 默认使用的是byType
* @Autowired的属性required,默认为true,表示如果引用类型赋值失败,则程序报错,终止运行
* required=false表示如果引用类型赋值失败,则将引用类型赋值为null
* 推荐使用required=true
*/
@Autowired
private PlayerP2 playerP2;
/**
* @Autowired 的byName方式
* 添加@Qualifier,value=“bean的id”
*/
@Autowired
@Qualifier("myPlayer")
private PlayerP2 playerP2;
/**
* @Resource 给引用类型添加注解,用法与@Autowired相似,但是它是JDK中的注解,JDK11以上使用此注解需添加依赖,默认是byName方式
*/
@Resource(name="bean的id")
private PlayerP2 playerP2;
//也可以在set方法上设置,很少用这个方式
@Value("ggg")
public void setpNick(String pNick) {
this.pNick = pNick;
}
}
注解与配置文件相结合的方式:
//配置文件配置key=value
player=myPlayer
//容器配置文件添引入配置文件
<context:property-placeholder location="classpath:annoTest1.properties"></context:property-placeholder>
//在使用注解的地方使用${key}方式引入
@Component("${player}")
public class PlayerP2 {...}
bean标签方式与注解方式的对比:
- bean标签方式更加符合OCP原则,耦合度更低,代码繁琐
- 注解方式代码更简洁,耦合度高,直接在类或者属性上方,方便阅读代码
7.动态代理
创建接口
public interface MovieProxyInter {
public void seeMovie();
public void buyTicket();
}
实现接口
public class MovieProxy implements MovieProxyInter{
@Override
public void seeMovie(){
System.out.println("看电影");
}
@Override
public void buyTicket(){
System.out.println("买电影票");
}
}
创建代理类
public class MyinvocationHandle implements InvocationHandler {
private Object target;
public MyinvocationHandle(Object target){
this.target=target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object res=null;
System.out.println("执行了invoke方法");
if("seeMovie".equals(method.getName())){
//在执行目标方法之前调用记录日志方法
Util.doLog();
//target是目标方法所属的对象,args是对象的各个方法
res=method.invoke(target,args);
//在执行目标方法之后执行其他方法
Util.doCommit();
}else{
res=method.invoke(target,args);
}
return res;
}
}
动态代理调用方法
@Test
public void test7(){
MovieProxyInter mp=new MovieProxy();
InvocationHandler handle=new MyinvocationHandle(mp);
MovieProxyInter proxy=(MovieProxyInter)Proxy.newProxyInstance(mp.getClass().getClassLoader(),mp.getClass().getInterfaces(),handle);
proxy.seeMovie();
proxy.buyTicket();
}
动态代理的实现方式
- JDK动态代理:使用JDK中的Proxy,Method,InvocationHandler创建代理对象,动态代理要求目标类必须实现接口
- cglib动态代理:第三方的工具库,创建代理对象,原理是继承,通过继承目标类,创建子类,子类就是代理对象,要求目标类不能是final的,子类也不能是final的
当类实现了接口spring就会自动使用jdk动态代理,当类没有实现接口,则spring会自动使用cglib动态代理
动态代理的作用:
- 在目标类源代码不改变的情况下,增加功能;
- 减少代码的重复;
- 让目标类专注业务逻辑代码
- 让业务功能与日志、事物等非业务功能分离开来,解耦合
8.AOP面向切面编程
实际开发中很多方法可能都要执行相同的部分功能,例如记录日志、判断权限等,如果每个业务方法都写上这些功能,那么代码将会变得冗余,此时我们可以将这些业务方法中相同功能的代码切出来,形成块,例如日志功能一块,权限判断功能一块,在这些功能块中匹配业务方法,被匹配到的业务方法在执行的时候就会自动注入这些块功能,业务方法只需要编写业务代码,而不用每个业务功能都写一遍相同功能的代码,达到解耦的目的,这种方式即是面向切面编程;
aop就是动态代理的规范化,把动态代理的实现步骤,方式都定义好,让开发人员统一使用一种方式实现动态代理;
术语:
- Aspect:切面,切面就是指把通知应用到切入点的过程;
- JoinPoint:连接点,表示允许使用增强的地方、位置,比如切面的前、后、异常位置;
- Pointcut:切入点,被execution表达式匹配到的目标方法,就是实际被增强的方法;
- 目标对象:被代理的类就目标对象;
- Advice:通知,切面在某个具体的连接点采取的行为或行动,称为通知,例如日志记录、权限判断功能等,切面的核心逻辑代码都写在通知中,通知是切面功能的具体实现,通常是业务代码以外的需求,如日志、验证等,这些被模块化的特殊对象;
切面的功能代码、被增强的方法、切面的执行时机是切面的三要素;
使用场景:如果要批量的给一些方法添加一些功能、例如日志、权限判断功能,可以使用aop;
9.AspectJ
spring在内部实现了aop规范,主要是在事物处理时使用aop,我们在项目开发中很少使用spring的aop实现,因为spring的aop比较笨重、繁琐;
实际开发中通常使用aspectJ实现aop,aspectJ是一个开源的专门做aop的框架;
spring框架中集成了aspectj框架;
aspjectJ框架实现aop的两种方式:
- xml配置文件,配置全局事务的时候使用
- 注解,一般使用此方式
切面的执行时机Advice在aspectJ框架中使用注解表示:
- @Before
- @AfterReturning
- @Around
- @AfterThrowing
- @After
也可以使用xml标签表示
AspectJ匹配切入点的表达式:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throw-pattern?)
modifiers-pattern:访问权限类型,可省略
ret-type-pattern:返回值类型,不可省略
declaring-type-pattern:包名类名,可省略
name-pattern(param-pattern):方法名(参数类型和参数个数),不可省略
throw-pattern:抛出异常类型,可省略
?表示可选
以上表达式共4个部分 execution(访问权限 方法返回值 方法的声明(参数) 异常类型)
各部分之间可以使用以下符号
*表示0至多个任意字符
…用在方法参数中表示任意多个参数,用在包名后表示当前包及其子包路径
+用在类名后表示当前类及其子类,用在接口后表示当前接口及其实现类
execution(public * *(..)) 表示匹配任意公开方法
execution(* set*(..)) 表示匹配所有方法名以set开头的方法
execution(* com.xyz.service.*.*(..)) 表示匹配在com.xyz.service包中的所有类的所有方法
execution(* com.xyz.service..*.*(..)) 表示匹配在com.xyz.service包及其子包中的所有类的所有方法
execution(* *..service.*.*(..)) 表示匹配所有包下的service子包下的所有类的所有方法
execution(* *.service.*.*(..)) 表示匹配所有一级包下的service子包下的所有类中的所有方法
使用aspectJ实现aop的步骤
-
添加aspectJ依赖
-
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.3.13</version> </dependency>
-
创建目标类,目标类实现的接口
-
public interface MovieJ1 { public void seeMovie(String name,String addr); } public class MovieJ1Impl implements MovieJ1 { @Override public void seeMovie(String name,String addr) { System.out.println(name+"在"+addr+"看电影了哦"); } }
-
编写目标要增强的功能
-
创建切面类,在类的上面加入@Aspect
-
在类中定义要增强的方法,在方法的上方加入通知注解,例如@Before
-
指定切入点的匹配表达式
-
/** * @Aspect表示当前类是切面类 */ @Aspect public class AspectJ1 { /** * 定义实现切面功能的方法 * 方法的定义要求: * 1.必须是公共方法 * 2.不能有返回值 * 3.如果方法有参数,参数类型不能是自定义的,有几个类型可以使用 * * @Before:前置通知的注解 * 属性value:切入点execution表达式 * 在目标方法执行前执行 * 不会改变目标方法的执行结果 * 不会影响目标方法的执行 */ //@Before("execution(public void com.ywxk.spring5.aspectj.j1.MovieJ1Impl.seeMovie(String,String))") @Before("execution( * *..MovieJ1Impl.see*(..))") public void doLogBefore(){ System.out.println("切面方法记录日志,在切入点执行之前执行"); } }
-
-
在spring中通过注解或者bean标签声明切面类,目标类,aspectj的自动代理生成器(用来完成代理对象的自动创建功能)
<!--声明目标类-->
<bean id="movieJ1" class="com.ywxk.spring5.aspectj.j1.MovieJ1Impl"></bean>
<!-- 声明切面对象 -->
<bean id="aspectJ1" class="com.ywxk.spring5.aspectj.j1.AspectJ1"></bean>
<!-- 声明自动代理生成器
使用aspect框架内部的功能,创建目标对象的代理对象
返回的是目标对象被修改后的代理对象
aspectj-autoproxy会把容器中的所有对象,一次性都生成代理对象
当目标类已经实现了接口,想要使用cglib动态代理,可以设置参数proxy-target-class="true"
-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
<!--<aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>-->
调用
```java
@Test
public void test8(){
ApplicationContext context=new ClassPathXmlApplicationContext("applicationContext.xml");
MovieJ1 mj1Proxy=(MovieJ1) context.getBean("movieJ1");
mj1Proxy.seeMovie("某某某","大地影院");
}
10.通知(Advice)
通知的参数:JoinPoint,如果传入这个参数,这个参数必须放在参数列表的第一个位置;
@Before("execution(* *..MovieJ1Impl.*(..))")
public void doLogBefore2(JoinPoint joinPoint){
//获取方法的标签(定义)
System.out.println("方法的定义--"+joinPoint.getSignature());
//获取方法的名称
System.out.println("方法的名称--"+joinPoint.getSignature().getName());
//获取方法的所有参数
Object[] pointArgs=joinPoint.getArgs();
for (Object arg:pointArgs) {
System.out.println(arg);
}
}
后置通知,有JoinPoint参数
/**
* @AfterReturning:后置通知的注解,有两个属性值value,returning
* value属性:execution表达式
* returning属性:目标方法的返回值变量,变量名与通知方法的形参名相同
* 方法必须是public void
* 方法有参数 参数类型推荐是Object
* 在目标方法执行之后执行此方法
* 能够获取到目标方法的返回值 Object res,并进行修改,如果返回值是对象,对该返回值的修改会同步到目标方法的返回值,因为对象是引用传递
* @param res
*/
@AfterReturning(value = "execution(* *..MovieJ1Impl.getDirector(..))",returning = "res")
public void doAfterReturning2(Object res){
//res=(Director)res;
((Director) res).setName("叶问");
System.out.println("x修改后director"+res);
}
@Override
public Director getDirector() {
ApplicationContext context =new ClassPathXmlApplicationContext("applicationContext.xml");
Director d =(Director)context.getBean("director");
return d;
}
环绕通知
@Override
public Object buyTicket(String name1,String name2) {
System.out.println("目标方法执行了");
return "买电影票";
}
/**
* @Around 环绕通知,在目标方法前后都能增强,能控制目标方法是否被执行,修改原来目标方法的执行结果,影响目标方法的调用结果,是功能最强的通知
* 等同于JDK动态代理的InvocationHandler接口,ProceedingJoinPointc参数相当于InvocationHandler的Method参数
* 必须是public
* 必须有返回值,建议是Object
* 方法有固定参数ProceedingJoinPoint,继承了JoinPoint
* 环绕通知通常用来做事务操作,在目标方法执行前开启事务,在目标方法执行后提交事务
*/
@Around("execution(* *..buyTicket(..))")
public Object doArround(ProceedingJoinPoint pjp) throws Throwable {
System.out.println(pjp.getSignature().getName());
Object[] args= pjp.getArgs();
for (Object s: args) {
System.out.println(s);
}
System.out.println("在目标方法执行前执行某某功能");
//控制目标方法的调用,相当于method.invoke()
Object res=null;
if(1==1){
res=pjp.proceed();
}
System.out.println("在目标方法执行后执行某某功能");
return "修改了目标方法的返回值"+res;
}
@Test
public void test10(){
ApplicationContext context=new ClassPathXmlApplicationContext("applicationContext.xml");
MovieJ1 mj1Proxy=(MovieJ1) context.getBean("movieJ1");
Object d =mj1Proxy.buyTicket("路人甲","路人乙");
System.out.println(d);
}
异常通知,有JoinPoint属性
@Override
public void exceptionTest() {
//int a=10/0;
System.out.println("目标方法异常通知演示");
}
/**
* @AfterThrowing异常通知
* 必须是public void
* 参数Exception,可选参数JoinPoint
* 属性:
* value:execution表达式
* throwing:目标方法抛出的异常对象,变量名自定义,必须与方法中的接收参数名一致
* 在目标方法抛出异常时执行
* 可以用来做异常的监控程序,如果目标方法有异常,发送短信、邮件等
* 原理是 try...catch(){myAfterThrowing()},在catch语句里面执行该方法
* 实际开发中很少使用
* @param ex
*/
@AfterThrowing(value = "execution(* *..exceptionTest(..))",throwing = "ex")
public void myAfterThrowing(Exception ex){
System.out.println("目标方法抛出了异常"+ex);
}
@Test
public void test11(){
ApplicationContext context=new ClassPathXmlApplicationContext("applicationContext.xml");
MovieJ1 mj1Proxy=(MovieJ1) context.getBean("movieJ1");
mj1Proxy.exceptionTest();
}
最终通知,有JoinPoint属性
@Override
public void finnalTest() {
int a=10/0;
System.out.println("目标方法演示最终通知");
}
/**
* @After:最终通知,总会在目标方法执行后执行,相当于try...catch...finnaly(myAfter()),在finnaly语句里面执行该方法
* 一般做资源清除工作,实际开发中很少使用
* 必须是public void
*/
@After(value = "execution(* *..finnalTest(..))")
public Object myAfter(){
System.out.println("myafter执行了吗");
return 123;
}
//测试方法参考上面代码
@PointCut定义切入点
@After(value = "myPointCut()")
public void myAfter(){
System.out.println("myafter执行了吗");
}
@Before(value = "myPointCut()")
public void myBefore(){
System.out.println("myBefore执行了吗");
}
/**
* 当多个切入点表达式都指向同一个目标方法时,可以用@PointCut的切入点表达式指向这个目标方法,其他表达式引用@Pointcut注解的方法名即可
* 这个方法一般是私有的,因为这个方法不需要被外部调用
*/
@Pointcut("execution(* *..finnalTest(..))")
private void myPointCut(){
//无需代码
}
11.Spring整合Mybatis
步骤:
- 添加spring依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.18</version>
</dependency>
- 添加mybatis依赖
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.7</version>
</dependency>
- 添加mysql驱动
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.38</version>
</dependency>
- 添加spring事务依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.3.18</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.19</version>
</dependency>
- 添加mybatis和spring的集成依赖,mybatis官方提供的,用来在spring项目中创建mybatis的SqlSessionFactory、dao对象
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.6</version>
</dependency>
- 创建实体类
public interface MovieDao {
int insertMovie(Movie movie);
List<Movie> selectMovie();
}
- 创建dao接口和mapper文件
<mapper namespace="com.ywxk.spring.dao.MovieDao">
<select id="selectMovie" resultType="com.ywxk.spring.domain.Movie">
select * from y_mobie
</select>
<insert id="insertMovie">
insert into y_movie(m_name,m_up_year,m_director,m_adddate)
values (#{m_name},#{m_up_year},#{m_director},#{m_adddate})
</insert>
</mapper>
- 创建mybatis主配置文件
<configuration>
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
<!-- 设置别名 -->
<typeAliases>
<package name="com.ywxk.spring.domain"/>
</typeAliases>
<mappers>
<package name="com.ywxk.spring.dao"/>
</mappers>
</configuration>
- 创建Service接口和实现类
public class MovieServiceImpl implements MovieService {
@Autowired
private MovieDao movieDao;
@Override
public int addMovie(Movie movie) {
return movieDao.insertMovie(movie);
}
@Override
public List<Movie> selectMovie() {
List<Movie> movies=movieDao.selectMovie();
return movies;
}
}
-
创建srping配置文件,声明mybatis对象交给spring容器
- 数据源对象
- SqlSessionFactory
- Dao对象
- 声明自定义的service
<!-- 声明数据库配置文件路径 --> <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder> <!-- 声明数据源 --> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> <property name="url" value="${jdbc.url}"></property> <property name="username" value="${jdbc.username}"></property> <property name="password" value="${jdbc.password}"></property> <property name="maxActive" value="${jdbc.maxActive}"></property> </bean> <!-- 声明mybatis的SqlSessionFactoryBean,spring用这个类创建SqlSessionFactory对象 --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <!-- 引用类型set注入连接池 --> <property name="dataSource" ref="dataSource"></property> <!-- configLocation属性比较特殊,用value指定值 --> <property name="configLocation" value="classpath:mybatis.xml"></property> </bean> <!-- mybatis提供的,他会在内部调用getMapper,生成每个dao接口的代理对象放入spring容器中使用 需要传入两个值,sqlSessionFactory的id,dao接口的包路径 --> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property> <property name="basePackage" value="com.ywxk.spring.dao"></property> </bean> <!-- 声明service --> <bean id="movieService" class="com.ywxk.spring.service.impl.MovieServiceImpl"></bean>
-
创建测试类,获取Service对象,通过service调用dao完成数据库访问
@Test
public void test1(){
ApplicationContext context =new ClassPathXmlApplicationContext("applicationContext.xml");
Movie movie=new Movie();
movie.setM_name("星际穿越2");
movie.setM_up_year(2014);
movie.setM_director("克里斯托弗·诺兰2");
movie.setM_adddate(new Date());
MovieService movieService=(MovieService) context.getBean("movieService");
//spring和mybatis整合之后,事务是自动提交的,无需手动commit
int c=movieService.addMovie(movie);
System.out.println("数据库影响条数"+c);
List<Movie> movies=movieService.selectMovie();
for (Movie m:movies) {
System.out.println(m);
}
}
12.spring框架处理事务
事务的操作需要执行多个dao方法,所以事务应该放在业务方法上执行;
不同的数据库、框架执行事务的方式不同,每个项目用不同的事务操作方法导致代码看起来混乱,且程序员的学习成本提高,不利于项目的管理;
所以spring提供了一种事务处理的统一模型,使用统一的步骤整合了多个数据库框架的事务访问;
spring采用了声明式事务,把事务相关的资源和内容提供给spring,spring就能处理事务提交与回滚等操作,几乎不需要写代码;
spring使用事务管理器来进行在内部进行事务的提交、回滚操作,事务管理器由一个接口和接口的众多实现类组成;
事务管理器接口:PlateformTransactionManager,定义了事务的重要方法,commit,rollback等
事务管理器接口实现类:
- mybatis实现类:DataSourceTransactionManager
- hibernate实现类:HibernateTransactionManager
使用方法:在applicationContext.xml中配置即可
<bean id="xxx” class="...DataSourceTransactionManager">
当业务方法执行成功,没有异常抛出,spring会在方法执行后提交事务,事务管理器自动commit;
当业务方法抛出运行时异常或error,spring会执行回滚,调用事务管理器的rollback;
当业务方法抛出非运行时异常,主要是受检异常时,提交事物;
@Transactional注解是Spring框架提供的注解,放在public方法上方,表示当前方法具有事物,事务的隔离级别、传播行为、异常信息等可以在属性中配置,适用于中小型项目;
步骤:
-
<!-- 使用spring处理事物,声明事物管理器 --> <bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!-- 连接数据库,指定数据源 --> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 开启事务注解驱动,使用事务注解管理事务,transaction-manager="事务管理器id" --> <tx:annotation-driven transaction-manager="dataSourceTransactionManager"></tx:annotation-driven>
-
@Override //sping默认遇到运行时异常才会回滚 @Transactional(//如果存在rollbackFor属性指定的异常,一定会回滚) public void buy(int goodsId, int amount) {//事务代码}
13.aspectJ框架处理事务
在spring配置文件中声明类、方法需要的事务,使得业务方法和事务配置完全分离, 此方案适合大型项目,该方案的使用步骤全都在配置文件中完成;
步骤:
- pom文件中添加aspectj框架依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.13</version>
</dependency>
- applicationContext.xml中声明事务管理器
<!-- 使用spring处理事物,声明事物管理器 -->
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 连接数据库,指定数据源 -->
<property name="dataSource" ref="dataSource"></property>
</bean>
- 配置事务方法
<!-- 声明需要事务的业务方法并配置属性
id:该配置的id,唯一标识
-->
<tx:advice id="myAdvice" transaction-manager="dataSourceTransactionManager">
<!-- 配置事务属性 -->
<tx:attributes>
<!-- name:配置事务的完整方法名,不带包和类,也可以使用通配符 -->
<tx:method name="buy*" />
<tx:method name="add*"></tx:method>
<tx:method name="update*"></tx:method>
</tx:attributes>
</tx:advice>
- 配置需要AOP代理类,关联事务方法
<!-- 配置切入点表达式规则,指定需要切面的类和方法,然后关联事务方法 -->
<aop:config>
<!-- id:指定切入点(实际被增强的方法)的id,expression:excution表达式 ,指定需要进行事务的类 -->
<aop:pointcut id="servicePt" expression="execution(* *..service..*.*(..))"/>
<!-- 关联事务方法与切入点 -->
<aop:advisor advice-ref="myAdvice" pointcut-ref="servicePt"></aop:advisor>
</aop:config>