1、IOC:控制反转
1.1、HelloSpring
1.1.1 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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 使用alias标签可以给bean设置别名-->
<alias name="fromName" alias="toName"/>
<!--
bean:使用bean标签将bean交由IOC容器管理
id:唯一标识
class:bean的全类名
property:使用该标签为bean中的属性注入值(此处为set注入),bean中需要提供set方法
scope:作用域
singleton:单例(默认,每次获取bean是同一个)
prototype:原型(每次获取bean不是同一个)
name:使用name属性也可以设置别名
-->
<bean id="user" class="com.qf.entity.User" name="alias">
<property name="name" value="黄本伟"/>
</bean>
</beans>
1.1.2 bean的使用
@Test
public void test(){
//使用ClassPathXmlApplicationContext()加载Spring配置文件
ApplicationContext context=new ClassPathXmlApplicationContext("applicationContext.xml");
//使用getBean()获取bean对象
User user=context.getBean("user",User.class);
System.out.println("name:"+user.getName());
}
1.2、Dependency Injection:依赖注入
1.2.1 构造器注入:constructor-arg
<bean id="address" class="com.qf.entity.Address">
<property name="address" value="xxxxx"/>
</bean>
<!--
方式一:构造器注入
constructor-arg:通过该标签进行构造器注入
ref:引用类型注入
value:基本数据类型注入
-->
<bean id="user" class="com.qf.entity.User">
<constructor-arg ref="address"/>
<constructor-arg value="张三"/>
</bean>
1.2.2 Set注入:Property
<!--
方式二:Set注入,通过Set方法注入
-->
<bean id="user" class="com.qf.entity.User">
<!-- 基本数据类型-->
<property name="id" value="1"/>
<property name="name" value="xxx"/>
<!-- 引用数据类型-->
<property name="address" ref="address"/>
<!-- 数组类型-->
<property name="hobbies">
<array>
<value>听歌</value>
<value>玩游戏</value>
</array>
</property>
<!--
集合类型:collection
Set:set标签
List:List标签
-->
<property name="brothers">
<set>
<value>老大</value>
<value>老二</value>
</set>
</property>
<property name="loves">
<list>
<value>听歌</value>
<value>玩游戏</value>
</list>
</property>
<!-- Map类型-->
<property name="cards">
<map>
<entry key="身份证" value="111111222222223333"/>
<entry key="学生卡" value="112336544789"/>
</map>
</property>
<!-- Properties类型-->
<property name="config">
<props>
<prop key="111">111</prop>
<prop key="222">222</prop>
</props>
</property>
<!-- null值注入-->
<property name="wife">
<null/>
</property>
</bean>
- 测试代码
@Test
public void testUser(){
ApplicationContext context=new ClassPathXmlApplicationContext("applicationContext.xml");
User user=(User)context.getBean("user");
System.out.println(user);
}
/*
User(id=1, name=xxx,
hobbies=[听歌, 玩游戏], loves=[听歌, 玩游戏],
address=Address(address=xxxxx),
brothers=[老大, 老二],
cards={身份证=111111222222223333, 学生卡=112336544789},
wife=null,
config={222=222, 111=111})
*/
1.2.3 Autowired:自动注入
- autowire=“byName”
<!--
autowire="byName":根据名称匹配实现自动注入
-->
<bean id="userService" class="com.qf.service.impl.UserServiceImpl" autowire="byName"/>
- autowire=“byType”
<!--
autowire="byType":根据类型匹配实现自动注入
-->
<bean id="userService" class="com.qf.service.impl.UserServiceImpl" autowire="byType"/>
- @Autowired:自动装配
<?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
https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 使用注解需要引入context约束并且声明注解-->
<context:annotation-config/>
<bean id="userMapper" class="com.qf.mapper.impl.UserMapperImpl"/>
<bean id="userService" class="com.qf.service.impl.UserServiceImpl"/>
</beans>
public class UserServiceImpl implements UserService {
@Autowired
UserMapper userMapper;
public void setUserMapper(UserMapper userMapper) {
this.userMapper = userMapper;
}
@Override
public User selectUser() {
return this.userMapper.selectUser();
}
}
//测试
@Test
public void testUserService() {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = (UserService) context.getBean("userService");
System.out.println(userService.selectUser());
}
2、AOP
2.1 动态代理
2.1.1 JDK动态代理实现(基于接口)
- 需要被代理的类
public class UserMapperImpl implements IUserMapper {
@Override
public void add() {
System.out.println("UserMapperImpl.add");
}
@Override
public void delete() {
System.out.println("UserMapperImpl.delete");
}
@Override
public void update() {
System.out.println("UserMapperImpl.update");
}
@Override
public void insert() {
System.out.println("UserMapperImpl.insert");
}
}
- 调用Proxy.newProxyInstance()方法生成代理类
public class UserMapperProxy {
IUserMapper userMapper;
public UserMapperProxy(IUserMapper userMapper) {
this.userMapper = userMapper;
}
public IUserMapper getProxy() {
/*
获取代理类对象:
Proxy.newProxyInstance(ClassLoader loader,
@NotNull Class<?>[] interfaces,
@NotNull reflect.InvocationHandler h)
ClassLoader loader:类加载器
Class<?>[] interfaces:被代理类的接口
reflect.InvocationHandler h:处理接口,添加额外的功能,
需重写invoke(Object proxy, Method method, Object[] args)方法
Object proxy:生成的代理类对象
Method method:需要执行的方法
Object[] args:被执行方法的参数
*/
return (IUserMapper) Proxy.newProxyInstance(UserMapperProxy.class.getClassLoader(), this.userMapper.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("执行方法前。。。");
Object invoke = method.invoke(UserMapperProxy.this.userMapper, args);
System.out.println("执行方法前。。。");
return invoke;
}
});
}
}
- 测试
public class TestProxy {
@Test
public void testProxy() {
//初始化
UserMapperProxy userMapperProxy = new UserMapperProxy(new UserMapperImpl());
//获取代理类对象
IUserMapper proxy = userMapperProxy.getProxy();
//调用代理后的方法
proxy.add();
proxy.delete();
proxy.insert();
proxy.update();
/*
执行方法前。。。
UserMapperImpl.add
执行方法前。。。
*/
}
}
- 动态代理的好处
- 静态代理有的它都有,静态代理没有的,它也有!
- 可以使得我们的真实角色更加纯粹 . 不再去关注一些公共的事情 .
- 公共的业务由代理来完成 . 实现了业务的分工 ,
- 公共业务发生扩展时变得更加集中和方便 .
- 一个动态代理 , 一般代理某一类业务
- 一个动态代理可以代理多个类,代理的是接口!
2.2 AOP相关概念
- 横切关注点:跨越应用程序多个模块的方法或功能。即是,与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点。如日志 , 安全 , 缓存 , 事务等
- 切面(ASPECT):横切关注点 被模块化 的特殊对象。即,它是一个类。
- 通知(Advice):切面必须要完成的工作。即,它是类中的一个方法。
- 目标(Target):被通知对象。
- 代理(Proxy):向目标对象应用通知之后创建的对象。
- 切入点(PointCut):切面通知 执行的 “地点”的定义。
- 连接点(JointPoint):与切入点匹配的执行点。
2.3 使用spring实现AOP
2.3.1 导入AOP织入依赖
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
2.3.2 方式一:通过Spring API实现
- SpringAOP中,通过Advice定义横切逻辑,Spring中支持5种类型的Advice:
- 前置通知:MethodBeforeAdvice
- 后置通知:AfterReturningAdvice
- 环绕通知:MethodInterceptor
- 异常抛出通知:ThrowsAdvice
- 引介通知:IntroductionInterceptor
-
Aop在不改变原有代码的情况下 , 去增加新的功能
-
编写两个通知类,分别实现MethodBeforeAdvice接口和AfterReturningAdvice接口
@Component
public class BeforeLog implements MethodBeforeAdvice {
/**
* 前置通知
*
* @param method 要执行的目标对象的方法
* @param args 被调用的方法的参数
* @param target 目标对象
* @throws Throwable
*/
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println(target.getClass().getSimpleName() + "的" + method.getName() + "方法被执行了。。。");
}
}
@Component
public class AfterLog implements AfterReturningAdvice {
/**
* 带返回值的后置通知
*
* @param returnValue 返回值
* @param method 被调用的方法
* @param args 方法的参数
* @param target 目标对象
* @throws Throwable
*/
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("执行了" + target.getClass().getName()
+ "的" + method.getName() + "方法,"
+ "返回值:" + returnValue);
}
}
- 在spring配置文件中配置AOP切入实现,需要先导入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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/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">
<!-- 告知spring,哪些包中 有被注解的类、方法、属性 -->
<!-- <context:component-scan base-package="com.qf.a,com.xx.b"></context:component-scan> -->
<context:component-scan base-package="com.qf"/>
<!--aop的配置-->
<aop:config>
<!--切入点 execution表达式:表达式匹配要执行的方法-->
<aop:pointcut id="pointcut" expression="execution(* com.qf.service.impl.UserServiceImpl.*(..))"/>
<!--切入通知; advice-ref执行方法 . pointcut-ref切入点-->
<aop:advisor advice-ref="beforeLog" pointcut-ref="pointcut"/>
<aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
</aop:config>
</beans>
- 测试
@Test
public void test() {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = context.getBean("userServiceImpl", UserService.class);
userService.add();
userService.delete();
userService.update();
userService.select();
}
/*
UserServiceImpl的add方法被执行了。。。
UserServiceImpl.add
执行了com.qf.service.impl.UserServiceImpl的add方法,返回值:null
*/
2.3.3 自定义切面类实现
- 自定义的切面类
@Component
public class DiyPointCut {
public void before() {
System.out.println("执行方法前。。。");
}
public void after() {
System.out.println("执行方法后。。。");
}
}
- 在spring配置中配置切面
<!--方式二:-->
<!--切面:aop:aspect-->
<aop:aspect ref="diyPointCut">
<aop:pointcut id="pointcut" expression="execution(* com.qf.service.impl.UserServiceImpl.*(..))"/>
<!--
前置通知:aop:before
后置通知:aop:after
-->
<aop:before method="before" pointcut-ref="pointcut"/>
<aop:after method="after" pointcut-ref="pointcut"/>
</aop:aspect>
</aop:config>
2.3.4 使用注解实现
- 需要在spring配置文件中增加支持注解的配置
<!--增加aop注解支持-->
<!--
通过aop命名空间的<aop:aspectj-autoproxy />声明自动为spring容器中那些配置@aspectJ切面的bean创建代理,织入切面。
当然,spring 在内部依旧采用AnnotationAwareAspectJAutoProxyCreator进行自动代理的创建工作,
但具体实现的细节已经被<aop:aspectj-autoproxy />隐藏起来了
<aop:aspectj-autoproxy />有一个proxy-target-class属性,默认为false,
表示使用jdk动态代理织入增强,当配为<aop:aspectj-autoproxy poxy-target-class="true"/>时,
表示使用CGLib动态代理技术织入增强。不过即使proxy-target-class设置为false,
如果目标类没有声明接口,则spring将自动使用CGLib动态代理。
-->
<aop:aspectj-autoproxy/>
- 编写切面类
@Component
@Aspect
public class AnnotationPointcut {
@Before("execution(* com.qf.service.impl.UserServiceImpl.*(..))")
public void before() {
System.out.println("执行方法前。。。");
}
@After("execution(* com.qf.service.impl.UserServiceImpl.*(..))")
public void after() {
System.out.println("执行方法后。。。");
}
/**
* @param jp 连接点对象
* @throws Throwable
*/
@Around("execution(* com.qf.service.impl.UserServiceImpl.*(..))")
public void around(ProceedingJoinPoint jp) throws Throwable {
System.out.println("环绕前");
System.out.println("签名:" + jp.getSignature());
//执行目标方法proceed
Object proceed = jp.proceed();
System.out.println("环绕后");
System.out.println(proceed);
}
}
- 测试
@Test
public void test1() {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = context.getBean("userServiceImpl", UserService.class);
userService.add();
}
/*
环绕前
签名:void com.qf.service.UserService.add()
执行方法前。。。
UserServiceImpl.add
执行方法后。。。
环绕后
null
*/
3 事务
3.1 事务的ACID特性
- 原子性(atomicity)
- 事务是原子性操作,由一系列动作组成,事务的原子性确保动作要么全部完成,要么完全不起作用
- 一致性(consistency)
- 一旦所有事务动作完成,事务就要被提交。数据和资源处于一种满足业务规则的一致性状态中
- 隔离性(isolation)
- 可能多个事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏
- 持久性(durability)
- 事务一旦完成,无论系统发生什么错误,结果都不会受到影响。通常情况下,事务的结果被写到持久化存储器中
3.2 事务的隔离级别
isolation
隔离级别
名称 | 描述 |
---|---|
default | (默认值)(采用数据库的默认的设置) (建议) |
read-uncommited | 读未提交 |
read-commited | 读提交 (Oracle数据库默认的隔离级别) |
repeatable-read | 可重复读 (MySQL数据库默认的隔离级别) |
serialized-read | 序列化读 |
- 隔离级别由低到高为:read-uncommited < read-commited < repeatable-read < serialized-read
- 安全性:级别越高,多事务并发时,越安全。因为共享的数据越来越少,事务间彼此干扰减少。
- 并发性:级别越高,多事务并发时,并发越差。因为共享的数据越来越少,事务间阻塞情况增多。
3.3 事务并发问题
问题 | 描述 |
---|---|
脏读 | 一个事务读取到另一个事务还未提交的数据。大于等于 read-commited 可防止 |
不可重复读 | 一个事务内多次读取一行数据的相同内容,其结果不一致。大于等于 repeatable-read 可防止 |
幻影读 | 一个事务内多次读取一张表中的相同内容,其结果不一致。serialized-read 可防止 |
3.4 事务的传播行为
propagation
传播行为- 当涉及到事务嵌套(Service调用Service)时,可以设置:
- SUPPORTS = 不存在外部事务,则不开启新事务;存在外部事务,则合并到外部事务中。(适合查询)
- REQUIRED = 不存在外部事务,则开启新事务;存在外部事务,则合并到外部事务中。 (默认值)(适合增删改)
3.5 事务回滚
rollback-for
回滚属性- 如果事务中抛出 RuntimeException,则自动回滚
- 如果事务中抛出 CheckException(非运行时异常 Exception),不会自动回滚,而是默认提交事务
- 处理方案 : 将CheckException转换成RuntimException上抛,或 设置 rollback-for=“Exception”
3.6 spring配置声明式事务
- 配置事务管理器
<!-- 1. 引入一个事务管理器,其中依赖DataSource,借以获得连接,进而控制事务逻辑 -->
<bean id="tx" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
- 注意:DataSourceTransactionManager 和 SqlSessionFactoryBean 要注入同一个DataSource的Bean,否则事务控制失败!!!
- 配置事务通知
<tx:advice id="txManager" transaction-manager="tx">
<tx:attributes>
<!--<tx:method name="insertUser" rollback-for="Exception" isolation="DEFAULT"
propagation="REQUIRED" read-only="false"/>-->
<!-- 以User结尾的方法,切入此方法时,采用对应事务实行-->
<tx:method name="*User" rollback-for="Exception"/>
<!-- 以query开头的方法,切入此方法时,采用对应事务实行 -->
<tx:method name="query*" propagation="SUPPORTS"/>
<!-- 剩余所有方法 -->
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
- 切入事务
<aop:config>
<aop:pointcut expression="execution(* com.qf.spring.service.UserServiceImpl.*(..))" id="pc"/>
<!-- 组织切面 -->
<aop:advisor advice-ref="txManager" pointcut-ref="pc"/>
</aop:config>