文章目录
1、Spring
spring是一个轻量级、非入侵式的控制反转(IOC)和面向切面(AOP)的容器框架.
-
SpringBoot
- 一个快速开发的脚手架
- 基于SpringBoot可以快速的开发单个微服务
- 约定大于配置
-
SpringCloud
- 基于SpringBoot实现的
控制反转 IoC
IoC是一种设计思想,而DI(依赖注入)只是实现 IOC的其中一种方法。
IoC是一种通过描述(XML或者注解)并通过第三方去生产或获取特定对象的方式,在Spring中实现控制反转的是IoC容器,其实现方法是依赖注入(Dependency Injection)
Hello,Spring!
- 控制:谁来控制对象的创建!传统的应用程序的对象是由程序本身控制创建的,使用Spring后,对象是由Spring来创建的
- 反转:程序本身不创建对象,而变成被冻的接收对象
- 依赖注入:就是利用set方法进行注入
- IOC是一种编程思想,由主动的编程变为被动的接收。对象由Spring来创建、管理、装配!
Pojo:
package com.shen.pojo;
import lombok.Data;
@Data
public class Hello {
private String str;
}
Beans.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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!--使用spring来创建对象,在spring这些都称为bean-->
<bean id="hello" class="com.shen.pojo.Hello">
<property name="str" value="Hello,spring!"></property>
</bean>
</beans>
Test:
import com.shen.pojo.Hello;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
public static void main(String[] args) {
// 获取spring的上下文对象
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
// 我们的对象都在spring中管理了,我们要使用,直接去里面取出来就可以
Hello hello = (Hello)context.getBean("hello");
System.out.println(hello.toString());
}
}
IoC例子2:
ServiceImpl:
package com.shen.service;
import com.shen.dao.UserDao;
import lombok.Data;
@Data
public class UserServiceImpl implements UserService{
private UserDao userDao;
@Override
public void printDaoInfo() {
userDao.printInfo();
}
}
beans.xml:
<!--使用spring来创建对象,在spring这些都称为bean-->
<bean id="mysqlDao" class="com.shen.dao.UserDaoMysqlImpl"></bean>
<bean id="oracleDao" class="com.shen.dao.UserDaoOracleImpl"></bean>
<bean id="service" class="com.shen.service.UserServiceImpl">
<!--
ref: 引用Spring容器中创建好的对象
value:具体的值,基本数据类型!
-->
<property name="userDao" ref="oracleDao"></property>
</bean>
test:
public class MyTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
UserServiceImpl service = (UserServiceImpl)context.getBean("service");
service.printDaoInfo();
}
}
使用配置的好处在于,当需要修改set注入的内容的时候,不需要重启服务器,因为没有改动源代码,改的是xml配置!!而且解耦了,如果要增加新的实现,只需要写新的实现,并且替换配置。
IoC创建对象方式
-
默认走的是无参构造
-
可以使用有参构造
- 1.按参数类型
<bean id="service" class="com.shen.service.UserServiceImpl"> <constructor-arg type="java.lang.String" value="有参构造的bean"></constructor-arg> </bean>
缺点在于 如果有两个参数都是同类型,就不行了,所以不建议使用此法。(如果非要用的话,可以按顺序赋值)
- 2.按照下标
<constructor-arg index="0" value="有参构造的bean"></constructor-arg>
- 3.匹配参数名
<constructor-arg name="str" value="直接通过参数名赋值"></constructor-arg>
2、Spring配置
-
别名:alias,和bean是同一级别的标签。name,在bean标签内,也是别名,而且name可以取多个别名(用逗号、空格、分号进行分割)。
-
import,和bean是同一级别的标签。一般用于团队开发使用,可以将多个配置文件导入合并为一个。
<import resource="beans2.xml"/>
注:使用import导入的时候,如果遇到同名的东西,后面的会覆盖前面的(因为按照从上到下的顺序进行导入合并)
-
scope 在bean标签内,可以设置是否为单例模式(默认单例)
3、依赖注入
3.1 构造器注入
上面写过了
也可以用c命名空间
3.2 set方式注入(重点)
也可以用p命名空间
- 依赖:bean对象的创建依赖于spring这个容器
- 注入:bean对象中的所有属性,由容器注入
<bean id="student" class="com.shen.pojo.Student" name="stu">
<!--普通注入-->
<property name="name" value="孟特"></property>
<property name="id" value="4"></property>
<!--对象注入(bean注入)-->
<property name="address" ref="add"></property>
<!--数组注入-->
<property name="books">
<array>
<value>《西游记》</value>
<value>《三国演义》</value>
</array>
</property>
<!--list注入-->
<property name="hobbies">
<list>
<value>弹吉他</value>
<value>写代码</value>
</list>
</property>
<!--map注入-->
<property name="scopes">
<map>
<entry key="语文" value="90"></entry>
<entry key="数学" value="100"></entry>
</map>
</property>
<!--properties注入,key-value格式-->
<property name="info">
<props>
<prop key="学号">201920085211015</prop>
<prop key="sex">男</prop>
</props>
</property>
</bean>
<bean id="address" class="com.shen.pojo.Address" name="add">
<constructor-arg name="addressName" value="梆子井4503"></constructor-arg>
<!--set注入-->
<!--测试注入一个空字符串,和一个null-->
<property name="concludes">
<set>
<value>北京市</value>
<value>朝阳区</value>
<value></value>
<value>null</value>
<null></null>
<value>三里屯</value>
</set>
</property>
</bean>
3.3 拓展方式注入
p命名空间、c命名空间
需要引入第三方xml约束:
xmlns:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
3.4 Bean scope (作用域)
-
Singleton 单例
- 每次从容器中get的时候都会返回同一个对象
-
prototype 原型
- 每次从容器中get的时候都会产生一个新的对象,其实这就是原型模式
下面的这些只有在web中才能用
-
request
-
session
-
websocket
-
Application
4、Bean的自动装配(autowired)
spring不仅可以手动依赖,还可以实现自动依赖!
spring会在上下文中自动寻找,并且给bean装配属性
有三种装配的方式
- 在xml中显示配置
- 在java中显示配置
- 隐式的自动配置
自动装配的实现方法:
-
byName:会自动在容器上下文中查找,自己对象set方法后面的值对应的beanid
比如,当前bean对象有一个方法叫setCat,就会自动寻找一个id叫做的cat的bean对象,把它装配进去
-
byType:会自动在容器上下文中查找,与自己对象属性类型相同的bean
- 弊端:必须保证每个类型的实体全局唯一
5、使用注解进行自动装配
要使用注解须知:
- 导入约束
- 配置注解的支持
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org.schema/context/spring-context.xsd">
<context:annotation-config/>
</beans>
例:
<bean id="dog" class="com.shen.pojo.Dog" >
<property name="name" value="wang~"></property>
</bean>
<bean id="cat" class="com.shen.pojo.Cat" >
<property name="name" value="miao~"></property>
</bean>
<bean id="people" class="com.shen.pojo.People" />
public class People {
@Autowired
private Dog dog;
@Autowired
private Cat cat;
private String name;
@Override
public String toString() {
return "People{" +
"dog=" + dog +
", cat=" + cat +
", name='" + name + '\'' +
'}';
}
}
我们发现:没有set方法,仅使用注解,也可以成功注入!!这是为什么呢?
因为使用注解的时候,不是通过set注入,而是通过反射直接获取了变量名以后进行注入
小结
@Autowired既可以直接在属性上使用,也可以在set方式上使用。
@Autowired(required = false) 可以为null
科普
@Nullable 字段标记了这个注解,则这个字段可以为null而不报错!
如果自动装配环境比较复杂,无法通过一个注解完成的时候(比如一个类有多个bean实例),我们可以在@Autowired后面接一个@Qualifier(value = “dog111”) 来指定唯一的bean对象。
也可以不使用两个注解,而只使用@Resource(name = “xxx”),这个是java自带的注解,先通过名字匹配byName,不唯一的话再通过类型匹配byType。都找不到就报错了。
6、使用注解开发
在Spring4之后,要使用注解开发,必须保证aop的包已经导入
-
bean
package com.shen.pojo; import lombok.Data; import org.springframework.stereotype.Component; // @Component等价于 <bean id="user" class="com.shen.pojo.User"/> @Data @Component public class User { private String name; }
-
属性如何注入
@Component public class User { // @Value 相当于bean里的property的value @Value("沈航冉") private String name; }
-
衍生的注解
@Component有几个衍生注解,在我们的web开发中,会按照mvc三层架构分层
-
dao【@Repository】 功能是一样的,但我们习惯用@Repository给dao层的东西加注解,也代表它是一个组件(component)
-
service【@Service】
-
controller【@Controller】
这四个注解的功能都是一样的,都是代表将某个类注册到Spring中,装配Bean
-
-
自动装配
-
作用域
@Scope(“prototype”)
采用原型模式(默认为singleton)
-
小结
- xml更加万能,适用于任何场合,维护方便简单
- 注解 不是自己的类不能用,维护相对复杂
最佳实践:xml用来管理bean,注解只负责使用的注入。
使用注解记得需要开启注解的支持。
7、使用Java的方式配置Spring
现在我们不使用Spring的xml配置了,全权交给java来做。
JavaConfig是Spring的一个子项目,在Spring4之后,它成为了一个核心功能。
@Data
@Component
public class User {
@Value("沈阳的沈")
private String name;
}
// 这个也会被Spring容器托管,注册到容器中,因为他本来就是一个@Component
// @Configuration代表这是一个配置类,就和我们之前看的beans.xml一样的
@Configuration
public class ShenConfig {
// 注册一个bean 就相当于我们之前写的一个bean标签 默认单例
@Bean
public User getUser(){
return new User();
}
}
public class MyTest {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(ShenConfig.class);
User getUser = context.getBean("getUser", User.class);
System.out.println(getUser);
}
}
8、代理模式
和装饰器模式的语法完全一样,只是语意不一样!
SpringAOP的底层就是代理模式.
8.1、静态代理
代码步骤:
- 公共接口
- 真实角色
- 代理角色
- 客户端访问代理角色
和装饰器模式完全相同。
- 可以使真实角色更加纯粹
- 公共的事情交给代理角色来做
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aW48FLto-1625239701232)(/Users/shenhangran/Desktop/学习笔记/我的/Spring笔记.assets/image-20210629162314168.png)]
原来是纵向开发,在不改动原有DAO层代码的时候,想给DAO层加一个日志功能,就可以采取和Service层并列的新建一个代理模式的类来完成,这就是横向开发。
8.2、动态代理
动态代理的代理类是动态生成的,不是我们直接写好的。
动态代理分为两大类:
- 基于接口的动态代理
- JDK动态代理【接下来使用这个】
- 基于类的动态代理
- cglib
- java字节码实现:javasist
需要了解两个类:Proxy(代理),InvocationHandler(调用处理程序)
例子:
用来生成代理类(对象)的类,详情看注释:
package shen;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// 我们会用这个类来自动生成代理类----动态代理
public class ProxyInvocationHandler implements InvocationHandler {
// 被代理的接口
private Rent rent;
public void setRent(Rent rent) {
this.rent = rent;
}
// 生成得到代理类
public Object getProxy(){
// 三个参数:生成的代理类【放到哪】、【代理的那个类是什么类型的接口】、【如何生成(使用重写的invoke方法)】
return Proxy.newProxyInstance(this.getClass().getClassLoader(), rent.getClass().getInterfaces(), this);
}
// 处理代理实例,并返回结果(官方原话)
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 动态代理的本质,就是使用反射机制!
// 这里的rent是被代理的那个类
Object invoke = method.invoke(rent, args);
// 在这里使用中介独有的新增加的方法
seeHouse();
fare();
return invoke;
}
// 中介独有方法:带客户看房子
public void seeHouse(){
System.out.println("中介带用户看房子");
}
public void fare(){
System.out.println("中介收你中介费");
}
}
代码步骤:
- 设置要代理的类是谁,给它写一个set方法
- 利用要代理的类,生成得到代理类
- 处理代理实例,返回结果(添砖加瓦)
客户这样使用:
public class Client {
public static void main(String[] args) {
Host host = new Host(); // 真实角色
ProxyInvocationHandler proxyInvocationHandler = new ProxyInvocationHandler(); // 代理角色
proxyInvocationHandler.setRent(host);
Rent proxy = (Rent)proxyInvocationHandler.getProxy(); // 这里的proxy就是动态生成的,我们并没有写一个真实的代理类
proxy.rrrrent();
}
}
于是,我们发现了,上面写的ProxyInvocationHandler类==可以当做一个工具类了,因为它可以代理任何一个类呀!==比如要增加日志功能,只需要自顶一个log方法,并放到invoke方法里使用就可以了!
public void log(String msg){
System.out.println("[Debug]执行了"+msg+"方法");
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object invoke = method.invoke(rent, args);
log(method.getName());
return invoke;
}
通过method.getName()反射可以获取方法名呀!
动态代理的好处:
- 可以使真实角色的操作更加纯粹!不用关注一些公共的业务(比如log)
- 公共业务可以交给代理角色,实现了业务的分工!
- 公共业务发生扩展的时候,方便集中管理!
- 一个动态代理类代理的是一个接口,一般就是对应的一类业务
- 一个动态代理类可以代理多个类,只要实现了同一个接口就行,复用成本低。
使用spring实现aop,需要导入一个依赖包
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
8.3 使用spring的aop
方式1:使用原生API接口
写一个接口和一个要被代理的类,最终目的是给它增加日志功能。
public class UserServiceImpl implements UserService{
@Override
public void add() {
System.out.println("使用了add方法");
}
@Override
public void delete() {
System.out.println("delete");
}
@Override
public void update() {
System.out.println("使用了update方法");
}
@Override
public void query() {
System.out.println("使用了query方法");
}
}
使用原生API接口,定义在某方法执行前,我们要做的事情
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
public class Log implements MethodBeforeAdvice {
// method: 要执行的目标对象的方法
// objects:参数
// o:目标对象
@Override
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println(o.getClass().getName()+"的"+method.getName()+"被执行啦!");
}
}
使用xml配置aop,定义切入点(在哪里执行log方法),并且定义执行环绕增加(log方法是谁)
<bean id="userService" class="com.shen.UserServiceImpl"/>
<bean id="log" class="com.shen.log.Log"/>
<!--方式一:使用原生API接口-->
<!--配置aop-->
<aop:config>
<!--切入点:在哪个地方执行方法 , expression:表达式,execution(要执行的位置!* * * * *)-->
<aop:pointcut id="pointcut" expression="execution(* com.shen.UserServiceImpl.*(..))"/>
<!--执行环绕增加-->
<aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
</aop:config>
测试
注意,我们动态代理代理的是接口,而不是具体实现类,所以如果写成具体实现类会报错!
import com.shen.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
// 动态代理代理的是接口,而不是实现类!
// UserServiceImpl userservice = context.getBean("userService", UserServiceImpl.class);
UserService userservice = context.getBean("userService", UserService.class);
userservice.delete();
}
}
方式2:使用自定义类实现AOP
package com.shen.diy;
public class DiyPointCut {
public void before(){
System.out.println("===方法执行前===");
}
public void after(){
System.out.println("===方法执行后===");
}
}
将自定义类当做切面,配置到aop中
<!--方式二:自定义类-->
<bean id="diy" class="com.shen.diy.DiyPointCut"/>
<aop:config>
<!--自定义切面,ref:要引用的类-->
<aop:aspect ref="diy">
<!--切入点-->
<aop:pointcut id="pointcut" expression="execution(* com.shen.UserServiceImpl.*(..))"/>
<!--通知-->
<aop:before method="before" pointcut-ref="pointcut"/>
<aop:after method="after" pointcut-ref="pointcut"/>
</aop:aspect>
</aop:config>
很明显,第一种方法功能要强大的多。因为第一种用到了反射。
方式3:使用注解实现
自定义切面类,在上面使用注解
补充:环绕增强,详见代码。
package com.shen.diy;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
// 方式三:使用注解方式实现AOP
@Aspect // 标注这个类是一个切面
public class AnnotationPointCut {
@Before("execution(* com.shen.UserServiceImpl.*(..))") // 注解代表通知,其参数代表切入点
public void before(){
System.out.println("注解实现的方式执行之前");
}
}
// 在环绕增强中,我们可以给定一个参数,代表我们要获取处理切入的点:
@Around("execution(* com.shen.UserServiceImpl.*(..))")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("===环绕前===");
// 获得签名
Signature signature = proceedingJoinPoint.getSignature();
System.out.println("签名:"+signature);
// 执行方法
Object proceed = proceedingJoinPoint.proceed();
System.out.println("===环绕后===");
}
然后将这个类配置到xml中,成为bean对象。最后,开启注解支持
<!--方式三:使用注解实现AOP-->
<bean id="annotationPointCut" class="com.shen.diy.AnnotationPointCut"/>
<!--开启注解支持!-->
<aop:aspectj-autoproxy/>
结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IUg3Pl4g-1625239701234)(/Users/shenhangran/Desktop/学习笔记/我的/Spring笔记.assets/image-20210630231832789.png)]
8.4 名词总结
- 横切关注点:与业务逻辑无关的,我们要实现的功能的概述。比如“日志、安全、缓存等等”
- 切面(aspect):横切关注点被模块化的特殊对象。即,一个类
- 通知(advice):切面要完成的工作。即,一个方法
- 目标(target):被通知对象。即,被代理的类对象。
- 代理(proxy):向目标对象应用通知后创建的对象。即,使用动态代理模式创建出的代理类对象。
- 切入点(pointcut):切面通知执行的“地点”的定义。即,在哪里执行“通知”这个方法。
- 连接点(jointpoint):与切入点匹配的执行点。
9、整合Mybatis
步骤:
-
导入相关jar包
- junit
- mybatis
- mysql数据库
- spring相关的
- aop织入
- mybatis-spring
<dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.25</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.2</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.3.8</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.3.7</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.7</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.0.2</version> </dependency> </dependencies>
-
编写配置文件
-
测试
整合步骤:
将spring的配置和mybatis的配置都整合到spring中,首先需要创建一个spring-dao.xml
其实本质就是把mybatis里的datasource、sqlSessionFactory和sqlSession交给spring托管
<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:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--DataSource: 使用spring的数据源替换Mybatis的配置 c3p0 dbcp
在这里使用spring提供的jdbc
-->
<bean id="datasource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/smbms?useSSL=TRUE&useUnicode=TRUE&characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="jiawensili1029"/>
</bean>
<!--sqlSessionFactory-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="datasource"/>
<!--绑定mybatis配置文件-->
<property name="configLocation" value="classpath:mybatis-config2.xml"/>
<property name="mapperLocations" value="classpath:com/shen/mapper/*.xml"/>
</bean>
<!--这个SqlSessionTemplate相当于原来的sqlSession,template就是模板的意思。这个类需要注入一个sqlSession工厂-->
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<!--只能使用构造器注入,因为它没有set方法-->
<constructor-arg name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
<bean id="userMapper" class="com.shen.mapper.UserMapperImpl">
<property name="sqlSession" ref="sqlSession"/>
</bean>
</beans>
其中,userMapper是自定义的一个接口的实现类,它的作用就是,把sqlSession注入进来,然后使用它!而它本身又成为一个bean对象,方便我们spring来托管。
package com.shen.mapper;
import com.shen.pojo.User;
import org.mybatis.spring.SqlSessionTemplate;
import java.util.List;
public class UserMapperImpl implements UserMapper{
private SqlSessionTemplate sqlSession;
public void setSqlSession(SqlSessionTemplate sqlSession) {
this.sqlSession = sqlSession;
}
@Override
public List<User> getAllUser() {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
return mapper.getAllUser();
}
}
在配置文件中,我们注意下面这段:
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="datasource"/>
<!--绑定mybatis配置文件-->
<property name="configLocation" value="classpath:mybatis-config2.xml"/>
<property name="mapperLocations" value="classpath:com/shen/mapper/*.xml"/>
</bean>
其中,mapperLocations包含了所有mapper.xml,比如:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"htto://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.shen.mapper.UserMapper">
<select id="getAllUser" resultType="user">
select * from user_test
</select>
</mapper>
我们的sql语句就是在这里写的!
而configLocation则是原来mybatis的配置文件。
由于原来mybatis的配置文件有很多内容已经集合到spring-dao.xml里了,所以现在的mybatis配置文件只剩下“别名”和“设置”这两部分内容!
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"htto://mybatis.org/dtd/mybatis-3-config.dtd">
<!--configuration核心-->
<configuration>
<!--设置要留着,可以添加其他设置-->
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
<typeAliases>
<typeAlias type="com.shen.pojo.User" alias="user"/>
</typeAliases>
</configuration>
如果没有别名的话,写sql会比较麻烦,而设置里我们可以添加其他的设置,比如日志。
此外,由于除了userMapper这个bean对象以外,其他都是固定的,因此我们可以上面一部分内容单独拿出来固定在一个xml里,再新建一个总的application.xml,把上面那个xml通过resource的形式引入,这样就不会显得乱了!(解耦了)
<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:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--DataSource: 使用spring的数据源替换Mybatis的配置 c3p0 dbcp
在这里使用spring提供的jdbc
-->
<import resource="spring-dao.xml"/>
<bean id="userMapper" class="com.shen.mapper.UserMapperImpl">
<property name="sqlSession" ref="sqlSession"/>
</bean>
</beans>
注:sqlSessionTemplate是线程安全的
10、声明式事务
@Override
public List<User> mixFixed() {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
mapper.addUser(new User("shr", "shrshrshr", "沈航冉"));
mapper.deleteUser("shr");
List<User> allUser = mapper.getAllUser();
return allUser;
}
@Test
public void test(){
ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
UserMapperImpl userMapper = context.getBean("userMapper", UserMapperImpl.class);
List<User> allUser = userMapper.mixFixed();
for(User user:allUser){
System.out.println(user);
}
}
经过测试,我们明明把好几个任务混合起来,当做了一个“新的任务”,它是一个新的原子性,应该当做一个事务来处理,但我们这么跑下来,虽然程序会报错(我们故意写错了delete的sql语句),但是还是成功插入了一组数据,说明这个方法并不是满足ACID原则的,说明它没有完成事务的功能!
为此,我们要使用声明式事务。
补充:
- 声明式事务:AOP
- 编程式事务:需要在代码中进行事务的管理(使用try、catch、rollback等)
声明式事务需要在spring的配置文件里做,本质就是spring帮我们写好了一个事务管理器。
<!--配置声明式事务-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="datasource"/>
</bean>
切入点处的方法已经被tx实现了,所以我们导入tx依赖,就可以写成下面这样.
tx的作用就是实现了事务处理的一些方法,我们要把这些方法配置到使用的地方(切入点)
tx这一套东西相当于是一个切面(理解为一个bean对象,所以可以使用advice-ref来增加执行环绕)
<!--结合AOP实现事务的织入-->
<!--配置事务通知的类(需要在最上面导入tx)-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!--给哪些方法配置事务-->
<!--配置事务的传播特性:propagation-->
<tx:attributes>
<tx:method name="add" propagation="REQUIRED"/>
<tx:method name="delete" propagation="REQUIRED"/>
<!--查找不需要事务-->
<tx:method name="query" read-only="true"/>
<!--*代表全部-->
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<!--配置事务切入-->
<aop:config>
<aop:pointcut id="txpointcut" expression="execution(* com.shen.mapper.*.xml.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txpointcut"/>
</aop:config>
- 补充:配置事务的传播特性(七种):
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-prPOjvAk-1625239701235)(/Users/shenhangran/Desktop/学习笔记/我的/Spring笔记.assets/image-20210702205923362.png)]
经测试,发现执行mixFixed方法还是把东西插进去了,找到原因:
-
因为mixFixed方法并没有在mapper中注册,而只是在UserMapperImpl里写明了它是把好几个事情混在一起的方法,我们的配置事务作用域并没有包含UserMapperImpl,而只包含了所有的mapper底下的所有的xml里的东西。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lQLRfdyP-1625239701236)(/Users/shenhangran/Desktop/学习笔记/我的/Spring笔记.assets/image-20210702213239187.png)]
-
解决方案:
-
改为mapper底下所有文件,而不只是xml文件
<!--配置事务切入--> <aop:config> <aop:pointcut id="txpointcut" expression="execution(* com.shen.mapper.*.*(..))"/> <aop:advisor advice-ref="txAdvice" pointcut-ref="txpointcut"/> </aop:config>
-
使用的地方,统一为UserMapper.class,而不是UserMapperImpl.class
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Elvyeevm-1625239701237)(/Users/shenhangran/Desktop/学习笔记/我的/Spring笔记.assets/image-20210702214034558.png)]
不然会报错:
Bean named ‘xxx’ is expected to be of type ‘xxx’ but was actually of type 'com.sun.proxy.$Proxy14
-
tx中配置的name必须与方法名相同
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UAZW157X-1625239701238)(/Users/shenhangran/Desktop/学习笔记/我的/Spring笔记.assets/image-20210702214300647.png)]
-
经测试,此处注释掉mixFixed会导致失败.
当然,也可使直接使用*来匹配所有.