入门案例

引入beans、context、core、expression依赖

  1. 创建类

    1
    2
    3
    4
    5
    public class User {
    public void add(){
    System.out.println("User add method");
    }
    }
  2. spring配置文件配置创建的对象

    1
    <bean id="user" class="com.cuc.spring5.User" />
  3. 调用类中方法

    1
    2
    3
    4
    5
    6
    7
    8
    @Test
    public void test(){
    // 加载配置文件
    ApplicationContext context = new ClassPathXmlApplicationContext("com/cuc/spring5/aop/bean.xml");
    // 获取创建对象
    User user = context.getBean("user",User.class);
    user.add();
    }

IOC容器

什么是IOC?

控制反转,把对象创建和对象之间的调用过程,交给Spring进行管理,降低耦合度。

原始方式直接调用某个类中方法,如果被调用类方法名改变或路径等发生改变,则调用的路径和方法名同样得改变。所以耦合度高。

而IOC则使用xml解析、工厂模式、反射完成这一过程,IOC容器底层就是对象工厂,对对象进行统一管理。

IOC接口(BeanFactory)

BeanFactory

  1. 是Spring内部使用接口,一般不使用。
  2. 加载配置文件时不会创建对象,获取时才创建。
1
2
// 加载配置文件
BeanFactory context = new ClassPathXmlApplicationContext("com/cuc/spring5/aop/bean.xml");

ApplicationContext

  1. BeanFactory的子接口,提供更多更强大的功能。
  2. 加载配置文件时就会把在配置文件中的对象进行创建。
1
2
3
4
// 加载配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("com/cuc/spring5/aop/bean.xml");
// 另一种方式
ApplicationContext context = new FileSystemXmlApplicationContext("D:/IDEA/workSpace_1.2/Spring5/spring5_AOP/src/com/cuc/spring/aop/bean.xml");

IOC操作Bean管理

  1. Bean管理即创建对象注入属性
  2. 基于xml配置文件注解两种方式实现Bean管理

基于xml方式

创建对象

创建对象时默认执行无参构造方法。

1
2
3
<bean id="user" class="com.cuc.spring5.User" />
<bean id="userService" class="com.cuc.spring5.UserService" />
<bean id="userDao" class="com.cuc.spring5.UserDaoImp" />
属性 解释
id 唯一标识(自己取)
class 全路径或类路径
等等 … …

注入属性

注入方式

1)set方式注入

1
2
3
4
5
6
7
8
9
10
11
12
<bean id="book" class="com.cuc.spring5.Book">
<property name="bname" value="三体" />
<!-- null值 -->
<property name="address">
<null />
</property>
<!-- 特殊符号 -->
<property name="address" value="&lt;&gt;南京" /> <!-- 小于号/大于号 -->
<property name="address">
<value><![CDATA[<>南京]]></value>
</property>
</bean>

2)有参构造注入

1
2
3
<bean id="book" class="com.cuc.spring5.Book">
<constructor-arg name="bname" value="三体" />
</bean>

3)p名称空间注入(了解)

1
2
3
4
5
6
// UserService创建UserDao类型属性,生成set方法
private UserDao userDao;

public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
1
2
3
4
5
<bean id="userService" class="com.cuc.spring5.UserService">
<property name="userDao" ref="userDaoImp"></property>
</bean>
<!-- ref中的值为以下bean的id值 -->
<bean id="userDaoImp" class="com.cuc.spring5.UserDaoImp" />
1
2
3
4
5
6
7
8
// 部门类
public class Dept {
private String name;

public void setName(String name) {
this.name = name;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 员工类
public class Emp {
private String name;
private String sex;
// 员工属于某个部门
private Dept dept;

public void setDept(Dept dept) {
this.dept = dept;
}
public void setName(String name) {
this.name = name;
}
public void setSex(String sex) {
this.sex = sex;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
<!--    内部bean注入属性     -->
<bean id="emp" class="com.cuc.spring5.Emp">
<!--设置两个普通属性-->
<property name="name" value="Lucy"></property>
<property name="sex" value="女"></property>
<!--设置对象类型属性(嵌套即内部bean,也可以写成外部bean)-->
<property name="dept">
<bean id="dept" class="com.cuc.spring5.Dept">
<property name="name" value="安保部"></property>
</bean>
</property>
</bean>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!--    级联方式注入属性     -->
<bean id="emp" class="com.cuc.spring5.Emp">
<!--设置两个普通属性-->
<property name="name" value="Lucy"></property>
<property name="sex" value="女"></property>
<!--级联赋值-->
<!--(1) 第一种写法-->
<property name="dept" ref="dept"></property>
<!--(2) 第二种写法 需要在Emp类中生成dept的get方法-->
<property name="dept.name" value="技术部"></property>
</bean>
<bean id="dept" class="com.cuc.spring5.Dept">
<property name="name" value="财务部"></property>
</bean>
注入集合类型属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<!--集合类型属性注入-->
<bean id="stu" class="com.cuc.spring5.collectiontype.Student">
<!--1.数组类型属性注入-->
<property name="courses">
<!--或者使用list标签-->
<array>
<value>Java</value>
<value>数据库</value>
</array>
</property>
<!--2.List类型属性注入-->
<property name="list">
<list>
<value>111</value>
<value>222</value>
</list>
</property>
<!--3.Map类型属性注入-->
<property name="map">
<map>
<entry key="JAVA" value="java"></entry>
<entry key="PHP" value="php"></entry>
</map>
</property>
<!--4.Set类型属性注入-->
<property name="set">
<set>
<value>MySQL</value>
<value>Redis</value>
</set>
</property>

<!-- 5.注入集合类型,值为对象 -->
<property name="courseList">
<list>
<ref bean="course1"></ref>
<ref bean="course2"></ref>
</list>
</property>
</bean>
<!--创建多个course对象-->
<bean id="course1" class="com.cuc.spring5.collectiontype.Course">
<property name="name" value="Spring5框架"></property>
</bean>
<bean id="course2" class="com.cuc.spring5.collectiontype.Course">
<property name="name" value="Mybatis框架"></property>
</bean>
1
2
3
4
5
6
7
8
9
10
11
<!--  集合注入部分提取出来 (这里以list为例,其它同理) -->
<!--(1)在配置文件中引入名称空间util(添加上方包含util的两行语句)-->
<!--(2)使用util标签完成list集合注入提取-->
<util:list id="bookList">
<value>易筋经</value>
<value>九阴真经</value>
<value>九阳神功</value>
</util:list>
<bean id="book" class="com.cuc.spring5.collectiontype.Book">
<property name="list" ref="bookList"></property>
</bean>

工厂Bean

Spring有两种类型bean:普通bean定义什么类型就返回什么类型,以上所有bean都是普通bean,工厂bean(FactoryBean)定义类型和返回类型可以不一样。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.cuc.spring5.factorybean;

import com.cuc.spring5.collectiontype.Course;
import org.springframework.beans.factory.FactoryBean;

// 实现工厂bean方法
public class MyBean implements FactoryBean<Course> {

//定义返回bean
@Override
public Course getObject() throws Exception {
Course course = new Course();
course.setName("工厂");
return course;
}
}
1
<bean id="myBean" class="com.cuc.spring5.factorybean.MyBean"></bean>

Bean的作用域

  1. 设置创建bean实例是单实例还是多实例。
  2. 默认情况下创建的是单实例对象。
1
2
3
4
5
<!--scope="prototype"定义成多例-->
<!--scope 值是 prototype 时候,不是在加载 spring 配置文件时候创建对象,在调用getBean方法时候创建多实例对象-->
<bean id="book" class="com.cuc.spring5.collectiontype.Book" scope="prototype">
<property name="list" ref="bookList"></property>
</bean>

Bean生命周期

对象创建到销毁过程。

  1. 构造器创建bean实例(有参无参等)
  2. 为bean属性设置值和对其他bean的引用(调用set方法等)
  3. 初始化前将bean实例传给bean后置处理器
  4. 调用bean的初始化方法(需要配置初始化方法)
  5. 初始化后将bean实例传给bean后置处理器
  6. 使用bean(获取到对象)
  7. 容器关闭时,调用bean的销毁方法(需要配置销毁方法)
1
2
3
4
5
6
7
<bean id="order" class="com.cuc.spring5.bean.Order" init-method="init" destroy-method="destroy">
<property name="name" value="手机"></property>
</bean>

<!-- 配置后置处理器 -->
<!--会对当前xml的所有bean添加后置处理器-->
<bean id="myBeanPost" class="com.cuc.spring5.bean.MyBeanPost"></bean>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.cuc.spring5.bean;

public class Order {
private String name;

// 无参构造
public Order() {
System.out.println("第一步,执行无参构造创建bean实例");
}

public void setName(String name) {
this.name = name;
System.out.println("第二步,调用set方法设置属性值");
}

// 创建执行的初始化方法
public void init(){
System.out.println("第三步,执行初始化方法");
}

// 创建执行的销毁方法
public void destroy(){
System.out.println("第五步,执行销毁方法");
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 创建后置处理器类
public class MyBeanPost implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("初始化之前执行的方法");
return bean;
}

@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("初始化之后执行的方法");
return bean;
}
}
1
2
3
4
5
6
7
8
9
@Test
public void test(){
ApplicationContext context = new ClassPathXmlApplicationContext("com/cuc/spring5/bean/bean.xml");
Order order = context.getBean("order",Order.class);
System.out.println("第四步,获取创建bean实例对象");
System.out.println(order);
// 手动让bean实例销毁
((ClassPathXmlApplicationContext)(context)).close();
}

xml自动装配

之前都是手动装配属性,自动装配会根据属性名称或类型自动完成属性值注入。

1
2
3
4
5
6
7
8
9
10
<!--实现自动装配
bean标签属性autowire,配置自动装配
autowire属性常用两个值:
byName根据属性名注入 (注入值bean的id值得和属性名称一样)
byType根据属性类型注入
-->
<bean id="emp" class="com.cuc.spring5.autowire.Emp" autowire="byName">
<!--<property name="dept" ref="dept"></property>-->
</bean>
<bean id="dept" class="com.cuc.spring5.autowire.Dept"></bean>

外部属性文件

以数据库连接池为例,先引入druid依赖

1
2
3
4
prop.driverClass = com.mysql.jdbc.Driver
prop.url = jdbc:mysql://localhost:3306/userDb
prop.username = root
prop.password = root
1
2
3
4
5
6
7
8
9
10
11
12
13
<!--    将外部properties属性文件引入到spring配置文件中     -->
<!-- (1)引入context名称空间 -->

<!-- (2)引入外部属性文件 -->
<context:property-placeholder location="classpath:jdbc.properties" />

<!-- (3)配置连接池 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${prop.driverClass}" />
<property name="url" value="${prop.url}" />
<property name="username" value="${prop.username}" />
<property name="password" value="${prop.password}" />
</bean>

基于注解方式

依赖:aop

格式:@注解名称(属性名称=属性值,…)

目的:简化xml配置

创建对象

@Component:一般用于普通类上

@Service:一般用于业务层

@Controller:一般用于web层

@repository:一般用于dao或持久层上

实际上这些注解可以混用,没有固定哪个层必须用哪个注解。

1
2
3
4
<!--    开启组件扫描
如果扫描多个包,多个包使用逗号隔开
-->
<context:component-scan base-package="com.cuc.spring5.a,com.cuc.spring5.b" />

细节:

1
2
3
4
5
6
7
<!--    示例1
use-default-filters="false" 表示不适用默认的filter,而用自己配置的filter
-->
<context:component-scan base-package="com.cuc.spring5.a" use-default-filters="false">
<!--表示只扫描a包下的带controller注解的类-->
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
1
2
3
4
5
6
<!--    示例2
context:exclude-filter: 设置哪些内容不进行扫描
-->
<context:component-scan base-package="com.cuc.spring5.a">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
1
2
3
// value可以省略不写,默认为类名称首字母小写
@Service(value = "userService") // 等价于 <bean id="userService" class="...UserService" />
public class UserService { ... }

属性注入

@AutoWired:根据属性类型自动装配

@Qualifier:根据属性名称进行注入

@Resource:既可以根据类型也可以根据名称注入

@Value:注入普通类型属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Value("张三") // 注入普通属性
private String name;

// 定义userDao
@Autowired // 根据属性类型自动装配且不需要添加set方法
private UserDao userDao;

// 定义studentDao
@Resource // 根据类型注入
private StudentDao studentDao;

@Resource (name = "studentDaoImp1") // 根据名称注入
private StudentDao studentDao2;

@Autowired
@Qualifier(value = "studentDaoImp1") // 根据名称注入,需配合@Autowired使用 (因为同类型的有可能不止一个)
private StudentDao studentDao;

完全注解开发

即不使用xml,此时xml里只有(注解扫描)。

1)创建配置类,替代xml配置文件

1
2
3
4
5
@Configurable  // 将当前类作为配置类,替代xml配置文件
@ComponentScan(basePackages = {"com.cuc.spring5.c"}) // 配置扫描路径
public class SpringConfig {

}

2)编写测试类

1
2
3
4
5
6
7
@Test
public void test(){
// 加载配置类
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
BookService bookService = context.getBean("bookService", BookService.class);
bookService.add();
}

AOP

什么是AOP?

面向切面编程,利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高开发效率。

通俗描述:不通过修改源代码,在主干功能里添加新功能。

底层原理

使用动态代理,并且有两种情况。

1)有接口情况,使用JDK动态代理

创建接口实现类代理对象,增强类的方法。

image-20220702202822185

底层代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// JDK动态代理 (底层代码)
public class JDKProxy {
public static void main(String[] args) {
// 创建接口实现类代理对象


Class[] interfaces = {UserDao.class};
UserDao userDao = new UserDaoImp();

// 第一种写法
// Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new InvocationHandler() {
// @Override
// public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// return null;
// }
// });

// 第二种写法
UserDao dao = (UserDao) Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new UserDaoProxy(userDao));
int result = dao.add(1,2);
System.out.println("result:"+result);
}
}

// 创建代理对象代码
class UserDaoProxy implements InvocationHandler{

// 1. 创建的是谁的代理对象,把谁传过来
// 有参构造传递
private Object obj;
public UserDaoProxy(Object obj){
this.obj = obj;
}

// 增强的逻辑
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

// 方法之前处理
System.out.println(method.getName()+"方法之前执行,传递过来的参数为:"+ Arrays.toString(args));

// 被增强的方法执行
Object res = method.invoke(obj,args);

// 方法之后处理
System.out.println("方法之后执行");

return res;
}
}

2)无接口情况,使用CGLIB动态代理

创建子类的代理对象,增强类的方法。

image-20220702203150608

操作术语

  1. 连接点

    类里面哪些方法可以被增强,这些方法称为连接点。

  2. 切入点

    实际被增强的方法,称为切入点。

  3. 通知(增强)

    实际增强的逻辑部分称为通知(增强)

    • 前置通知
    • 后置通知
    • 环绕通知
    • 异常通知
    • 最终通知
  4. 切面

    是动作,把通知应用到切入点的过程。

准备工作

1)Spring框架一般基于AspectJ实现AOP操作

2)两种实现方式:

  1. 基于xml配置文件
  2. 基于注解

3)依赖:aop、aspects、cglib、aopalliance、aspectj.weaver

4)切入点表达式

作用:知道对哪个类里面的哪个方法进行增强

语法结构:execution([权限修饰符][返回类型][类全路径].[方法名称]([参数列表]))

举例:

1
2
# 任意修饰符,返回类型可不写,..代表参数列表。注意*后有空格
execution(* com.cuc.dao.BookDao.add(..))
1
2
# 对类中所有方法进行增强
execution(* com.cuc.dao.BookDao.*(..))
1
2
# 对所有类中所有方法进行增强
execution(* com.cuc.dao.*.*(..))

AspectJ注解

1
2
3
4
5
6
7
// 被增强的类
@Component
public class User {
public void add(){
System.out.println("User add method");
}
}
1
2
3
4
5
6
7
8
9
10
11
// 增强类
@Component
@Aspect
@Order(1) // 多个增强类对同一个方法进行增强,可以定义优先级,数字越小优先级越高
public class PersonProxy {

@Before(value = "execution(* com.cuc.spring5.aop.User.add(..))")
public void before(){
System.out.println("PersonProxy before method");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// 增强类
@Component
@Aspect // 生成代理对象
@Order(2)
public class UserProxy {
// 抽取相同切入点
@Pointcut(value = "execution(* com.cuc.spring5.aop.User.add(..))")
public void point(){}

// 前置通知
@Before(value = "point()")
public void before(){
System.out.println("UserProxy before method");
}

// 后置通知 (在add方法返回值之后执行,所以当add方法有异常,当前方法不会执行)
@AfterReturning(value = "point()")
public void afterReturning(){
System.out.println("UserProxy afterReturning method");
}

// 最终通知
@After(value = "point()")
public void after(){
System.out.println("UserProxy after method");
}

// 异常通知(add方法有异常才会执行)
@AfterThrowing(value = "point()")
public void afterThrowing(){
System.out.println("UserProxy afterThrowing method");
}

// 环绕通知
@Around(value = "point()")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
System.out.println("环绕前");

// 执行被增强的方法
proceedingJoinPoint.proceed();

// 当被增强方法有异常不会执行
System.out.println("环绕后");
}

}
1
2
3
4
5
6
<!--    开启注解扫描      -->
<context:component-scan base-package="com.cuc.spring5.aop" />

<!-- 开启Aspect生成代理对象 -->
<!--扫描有@Aspect的类生成代理对象-->
<aop:aspectj-autoproxy />

完全注解开发

1
2
3
4
5
6
7
// 完全使用注解开发
@Configurable
@ComponentScan(basePackages = {"com.cuc.spring5"})
@EnableAspectJAutoProxy(proxyTargetClass = true) // 扫描有@Aspect的类生成代理对象
public class AopConfig {

}

AspectJ配置文件

  1. 创建两个类,增强类和增强类,创建方法
  2. 在spring配置文件中创建两个类对象
  3. 在spring配置文件中配置切入点
1
2
3
4
5
6
// 被增强类
public class Book {
public void buy(){
System.out.println("Book buy method");
}
}
1
2
3
4
5
6
// 增强类
public class BookProxy {
public void before(){
System.out.println("BookProxy before method");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
<!--创建对象-->
<bean id="book" class="com.cuc.spring5.aopXml.Book"/>
<bean id="bookProxy" class="com.cuc.spring5.aopXml.BookProxy"/>
<!--配置aop增强-->
<aop:config>
<!--切入点-->
<aop:pointcut id="p" expression="execution(* com.cuc.spring5.aopXml.Book.buy(..))"/>
<!--配置切面-->
<aop:aspect ref="bookProxy">
<aop:before method="before" pointcut-ref="p"/>
</aop:aspect>
</aop:config>

JdbcTemplate

概述和准备

概述:Spring框架对JDBC进行封装,使用JdbcTemplate方便实现对数据库的操作。

准备:

1)依赖:druid、mysql-connector、spring-jdbc、spring-orm(整合mybatis需要,这里不需要)

2)Spring配置文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!--开启组件扫描-->
<context:component-scan base-package="com.cuc.spring5"/>

<!-- 数据库连接池 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
destroy-method="close">
<property name="url" value="jdbc:mysql:///user_db?characterEncoding=utf-8" />
<property name="username" value="root" />
<property name="password" value="root" />
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
</bean>

<!-- jdbcTemplate对象 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!--注入dataSource-->
<property name="dataSource" ref="dataSource"/>
</bean>

3)注入JdbcTemplate对象

1
2
3
4
5
6
7
8
@Repository
public class UserDaoImp implements UserDao {

// 注入jdbcTemplate
@Autowired
private JdbcTemplate jdbcTemplate;

}

功能实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
@Repository
public class UserDaoImp implements UserDao {

// 注入jdbcTemplate
@Autowired
private JdbcTemplate jdbcTemplate;

@Override
// 添加
public void add(User user) {
String sql = "insert into t_user set username=?,status=?";
Object[] args = {user.getUsername(),user.getStatus()};

// int update = jdbcTemplate.update(sql,user.getUsername(),user.getStatus());
// 或
int update = jdbcTemplate.update(sql,args);

// 返回的值表示影响的行数
System.out.println(update);
}

@Override
// 修改
public void update(User user) {
String sql = "update t_user set username=?,status=? where id=?";
Object[] args = {user.getUsername(),user.getStatus(),user.getId()};
int res = jdbcTemplate.update(sql,args);
System.out.println(res);
}

@Override
// 删除
public void delete(int id) {
String sql = "delete from t_user where id=?";
int update = jdbcTemplate.update(sql,id);
System.out.println(update);
}


@Override
// 查询记录数
public int findCount() {
String sql = "select count(*) from t_user";
Integer count = jdbcTemplate.queryForObject(sql,Integer.class);
return count;
}

@Override
// 查询返回对象
public User findOne(int id) {
String sql = "select * from t_user where id=?";
User user = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<User>(User.class), id);
return user;
}

@Override
// 查询返回集合
public List<User> findAll() {
String sql = "select * from t_user";
List<User> userList = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(User.class));
return userList;
}

@Override
// 批量添加
public void batchAdd(List<Object[]> batchArgs) {
String sql = "insert into t_user set username=?,status=?";
int[] ints = jdbcTemplate.batchUpdate(sql, batchArgs);
System.out.println(Arrays.toString(ints));
}

@Override
// 批量修改
public void batchUpdate(List<Object[]> batchArgs) {
String sql = "update t_user set username=?,status=? where id=?";
int[] ints = jdbcTemplate.batchUpdate(sql, batchArgs);
System.out.println(Arrays.toString(ints));
}

@Override
// 批量删除
public void batchDelete(List<Object[]> batchArgs) {
String sql = "delete from t_user where id=?";
int[] ints = jdbcTemplate.batchUpdate(sql, batchArgs);
System.out.println(Arrays.toString(ints));
}
}

事务

事务概述以及四个特性(ACID)等在MySQL速通文章讲了。

准备

1)依赖:spring-tx

2)Spring配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!--开启组件扫描-->
<context:component-scan base-package="com.cuc.spring5"/>

<!-- 数据库连接池 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
destroy-method="close">
<property name="url" value="jdbc:mysql:///user_db?characterEncoding=utf-8" />
<property name="username" value="root" />
<property name="password" value="root" />
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
</bean>

<!-- jdbcTemplate对象 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!--注入dataSource-->
<property name="dataSource" ref="dataSource"/>
</bean>

3)创建service以及dao的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
@Service
public class UserService {

@Autowired
private UserDao userDao;

public void accountMoney(){
// lucy少100
userDao.reduceMoney();
// marry多100
userDao.addMoney();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Repository
public class UserDaoImp implements UserDao {

// 注入jdbcTemplate
@Autowired
private JdbcTemplate jdbcTemplate;

// lucy转账100给marry
@Override
// 多钱
public void addMoney() {
String sql = "update t_account set money=money+? where username=?";
jdbcTemplate.update(sql,100,"marry");
}

@Override
// 少钱
public void reduceMoney() {
String sql = "update t_account set money=money-? where username=?";
jdbcTemplate.update(sql,100,"lucy");
}
}

事务管理介绍

  1. 通常将事务添加到service中
  2. Spring事务管理操作方式:编程式事务管理声明式事务管理
  3. 声明式事务管理:基于注解基于xml配置文件
  4. 在Spring进行声明式事务管理,底层使用AOP

编程式事务管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Service
public class UserService {

@Autowired
private UserDao userDao;

public void accountMoney(){

// 编程式事务管理 (不方便)
try {
// 第一步 开启事务

// 第二步 进行业务操作
// lucy少100
userDao.reduceMoney();

// 模拟异常
int i = 10/0;

// marry多100
userDao.addMoney();

// 第三步 没有发生异常,提交事务
}catch (Exception e){
// 第四步 发生异常,事务回滚
}
}
}

声明式事务管理(注解)

1
2
3
4
5
6
7
8
<!--创建事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入数据源-->
<property name="dataSource" ref="dataSource"/>
</bean>

<!--开启事务注解-->
<tx:annotation-driven transaction-manager="transactionManager" />
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Service
public class UserService {

@Autowired
private UserDao userDao;

// 转账
@Transactional // 声明式事务管理 可以添加到类上面,也可以添加在方法上面 (添加到类上面 表示类中所有函数都添加事务注解)
public void accountMoney(){

// lucy少100
userDao.reduceMoney();

// 模拟异常
int i = 10/0;

// marry多100
userDao.addMoney();
}
}

事务参数

1
@Transactional(propagation=Propagation.REQUIRED,isolation=Isolation.REPEATABLE_READ,timeout=-1,readOnly=false,)
参数 解释
propagation 事务传播行为 常用(required、required_new、supports等)
isolation 事务隔离级别(mysql学过了)
timeout 超时时间(秒单位),超时不提交则回滚
readOnly 是否只读,就是只能查询
rollbackFor 回滚,设置哪些异常进行回滚
noRollbackFor 不回滚,设置哪些异常不进行回滚

required:如果有事务在运行,当前的方法就在这个事务内运行,否则,就启动一个新的事务,并在自己的事务内运行。

image-20220702233727478

required_new:当前的方法必须启动新事务,并在自己的事务内运行,如果有事务正在运行,应该将它挂起。

image-20220702234127662

supports:如果有事务在运行,当前方法就在这个事务中运行,否则它可以不运行在事务中。

完全注解开发

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// 完全注解事务管理

@Configurable // 配置类
@ComponentScan(basePackages = "com.cuc.spring5") // 组件扫描
@EnableTransactionManagement // 开启事务
public class TxConfig {

// 创建数据库连接池
@Bean
public DruidDataSource getDruidDataSource(){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql:///user_db?characterEncoding=utf-8");
dataSource.setUsername("root");
dataSource.setPassword("root");
return dataSource;
}

// 创建jdbcTemplate对象
@Bean
public JdbcTemplate getJdbcTemplate(DataSource dataSource){
JdbcTemplate jdbcTemplate = new JdbcTemplate();
// 注入dataSource
jdbcTemplate.setDataSource(dataSource);
return jdbcTemplate;
}

// 创建事务管理器对象
@Bean
public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource){
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource);
return transactionManager;
}
}

声明式事务管理(xml)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<!--创建事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入数据源-->
<property name="dataSource" ref="dataSource"/>
</bean>

<!-- 使用xml配置事务(通常使用注解形式配置) -->

<!--配置通知-->
<tx:advice id="txAdvice">
<!--配置事务参数-->
<tx:attributes>
<!--指定哪种规格的方法上面添加事务-->
<tx:method name="accountMoney" progagation="REQUIRED"/>
<!--开头为account的方法-->
<!--<tx:method name="account*"/>-->
</tx:attributes>
</tx:advice>

<!--配置切入点和切面-->
<aop:config>
<!--配置切入点-->
<aop:pointcut id="pt" expression="execution(* com.cuc.spring5.service.UserService.accountMoney(..))"/>
<!--配置切面-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt" />
</aop:config>

Spring5新功能

整合日志框架

1)依赖:slf4j-api、log4j-slf4j-impl、log4j-core、log4j-api

2)创建log4j2.xml配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?xml version="1.0" encoding="UTF-8"?>
<!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->
<!--Configuration后面的status用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,可以看到log4j2内部各种详细输出-->
<configuration status="INFO">
<!--先定义所有的appender-->
<appenders>
<!--输出日志信息到控制台-->
<console name="Console" target="SYSTEM_OUT">
<!--控制日志输出的格式-->
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</console>
</appenders>
<!--然后定义logger,只有定义了logger并引入appender,appender才会生效-->
<!--root:用于指定项目的根日志,如果没有单独指定Logger,则会使用root作为默认的日志输出-->
<loggers>
<root level="info">
<appender-ref ref="Console"/>
</root>
</loggers>
</configuration>

3)可以手动输出日志

1
2
3
4
5
6
7
8
9
10
public class UserLog {

// 手动输出日志
private static final Logger log = LoggerFactory.getLogger(UserLog.class);

public static void main(String[] args) {
log.info("info信息");
log.warn("warn信息");
}
}

image-20220703135451657

Nullable注解

@Nullable注解可以使用在方法、属性、参数上面,表示方法返回值、属性值、参数值可以为空。

1
2
3
4
5
6
7
@Nullable
String getId();

@Nullable
private String name;

String getId(@Nullable String name);

函数式注册对象

1
2
3
4
5
6
7
8
9
10
11
12
// 函数式风格创建对象,交给spring进行管理
@Test
public void genericApplicationContextTest(){
// 创建GenericApplicationContext对象
GenericApplicationContext context = new GenericApplicationContext();
// 调用context的方法对象注册
context.refresh();
context.registerBean(Book.class,()->new Book());
// 获取注册的对象
Book book = (Book) context.getBean("com.cuc.spring5.test.Book");
System.out.println(book);
}

整合JUnit5单元测试框架

依赖:spring-test

  • 之前每次测试都需要写一长串代码
1
2
3
4
5
6
7
8
@Test
public void test(){
// 加载配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("com/cuc/spring5/aop/bean.xml");
// 获取创建对象
User user = context.getBean("user",User.class);
user.add();
}
  • 整合JUnit4:

需要引入JUnit4依赖:junit、hamcrest-core

1
2
3
4
5
6
7
8
9
10
@RunWith(SpringJUnit4ClassRunner.class) // 指定单元测试框架
@ContextConfiguration("classpath:bean.xml") //加载配置文件
public class JTest4 {
@Autowired
private UserService userService;
@Test
public void test(){
userService.accountMoney();
}
}
  • 整合JUnit5:

依赖:image-20220703141411048

1
2
3
4
5
6
7
8
9
10
11
12
// @ExtendWith(SpringExtension.class)
// @ContextConfiguration("classpath:bean.xml")

@SpringJUnitConfig(locations = "classpath:bean.xml") // 复合上面两个注解
public class JTest5 {
@Autowired
private UserService userService;
@Test
public void test(){
userService.accountMoney();
}
}

Webflux

还没学