该笔记来自B站SGG-Spring5学习记录及Spring中文网,文章末贴出链接
2022.05.21
面向切面编程(AOP)
AOP 的全称是“Aspect Oriented Programming”,译为“面向切面编程”,和 OOP(面向对象编程)类似,它也是一种编程思想。
与 OOP 中纵向的父子继承关系不同,AOP 是通过横向的抽取机制实现的。它将应用中的一些非业务的通用功能抽取出来单独维护,并通过声明的方式(例如配置文件、注解等)定义这些功能要以何种方式作用在那个应用中,而不是在业务模块的代码中直接调用。
通俗来讲:AOP就是不通过修改源代码方式,在主干功能里面添加新功能
AOP实现(框架)
目前最流行的 AOP 实现(框架)主要有两个,分别为 Spring AOP 和 AspectJ。
1. Spring AOP (动态AOP)
是一款基于 AOP 编程的框架,它能够有效的减少系统间的重复代码,达到松耦合的目的。
Spring AOP 使用纯 Java 实现,不需要专门的编译过程和类加载器,在运行期间通过代理方式向目标类植入增强的代码。Spring AOP 支持 2 种代理方式:
1.1 分别是基于接口的 JDK 动态代理
(Spring AOP 默认的动态代理方式,若目标对象实现了若干接口,Spring 使用 JDK 的 java.lang.reflect.Proxy 类进行代理。)
1.2 基于继承的 CGLIB 动态代理。
(若目标对象没有实现任何接口,Spring 则使用 CGLIB 库生成目标对象的子类,以实现对目标对象的代理。)
2. AspectJ (静态AOP)
是一个基于 Java 语言的 AOP 框架,从 Spring 2.0 开始,Spring AOP 引入了对 AspectJ 的支持。
AspectJ 扩展了 Java 语言,提供了一个专门的编译器,在编译时提供横向代码的植入。
AOP术语
1. 连接点
类里面哪些方法可以被增强,这些方法称为连接点
2. 切入点
实际被真正增强的方法,称为切入点
3. 通知(增强)
3.1 实际增强的逻辑部分称为通知(增强)
3.2 通知有多种类型
前置通知
后置通知
环绕通知
异常通知
最终通知
4. 切面
是动作,把通知应用到切入点过程
Spring AOP
1. 机制
Spring 在运行期会为目标对象生成一个动态代理对象,并在代理对象中实现对目标对象的增强。
2. 连接点
Spring AOP 并没有像其他 AOP 框架(例如 AspectJ)一样提供了完成的 AOP 功能,它是 Spring 提供的一种简化版的 AOP 组件。其中最明显的简化就是,Spring AOP 只支持一种连接点类型:方法调用。
3. 连接点扩展
如果需要使用其他类型的连接点(例如成员变量连接点),我们可以将 Spring AOP 与其他的 AOP 实现一起使用,最常见的组合就是 Spring AOP + ApectJ。
4. 通知类型
Spring AOP 按照通知(Advice)织入到目标类方法的连接点位置,为 Advice 接口提供了 6 个子接口, 如下:
4.1 前置通知 org.springframework.aop.MethodBeforeAdvice // 在目标方法执行前实施增强。
4.2 后置通知 org.springframework.aop.AfterReturningAdvice // 在目标方法执行后实施增强。
4.3 后置返回通知 org.springframework.aop.AfterReturningAdvice // 在目标方法执行完成,并返回一个返回值后实施增强。
4.4 环绕通知 org.aopalliance.intercept.MethodInterceptor // 在目标方法执行前后实施增强。
4.5 异常通知 org.springframework.aop.ThrowsAdvice // 在方法抛出异常后实施增强。
4.6 引入通知 org.springframework.aop.IntroductionInterceptor // 在目标类中添加一些新的方法和属性。
5. 切面类型
Spring 使用 org.springframework.aop.Advisor 接口表示切面的概念,实现对通知(Adivce)和连接点(Joinpoint)的管理。
在 Spring AOP 中,切面可以分为三类:一般切面、切点切面和引介切面。
5.1 一般切面 org.springframework.aop.Advisor // Spring AOP 默认的切面类型。
由于 Advisor 接口仅包含一个 Advice(通知)类型的属性,而没有定义 PointCut(切入点),因此它表示一个不带切点的简单切面。这样的切面会对目标对象(Target)中的所有方法进行拦截并织入增强代码。由于这个切面太过宽泛,因此我们一般不会直接使用。
5.2 切点切面 org.springframework.aop.PointcutAdvisor // Advisor 的子接口,用来表示带切点的切面,该接口在 Advisor 的基础上还维护了一个 PointCut(切点)类型的属性。使用它,我们可以通过包名、类名、方法名等信息更加灵活的定义切面中的切入点,提供更具有适用性的切面。
5.3 引介切面 org.springframework.aop.IntroductionAdvisor // Advisor 的子接口,用来代表引介切面,引介切面是对应引介增强的特殊的切面,它应用于类层面上,所以引介切面适用 ClassFilter 进行定义。
基于接口的JDK动态代理示例(Spring AOP)
// 1. 创建接口及其方法
public interface UserService {
int queryCount(int x, int y);
}
// 2. 创建其实现类及实现方法
public class UserServiceImpl implements UserService {
@Override
public int queryCount(int x, int y) {
System.out.println("正常方法被执行..");
return x + y;
}
}
public class JDKProxy {
public static void main(String[] args) {
// 3. 使用Proxy类创建接口代理对象
// 需要引入jdk中的lang包下的reflect.Proxy类
// 调用newProxyInstance方法
// 参数1:类加载器
// 参数2:需要被增强方法所在的类,这个类实现的接口,支持多个接口
// 参数3:实现这个接口InvocationHandler,创建代理对象,写增强的部分
// 返回一个被包装(被增强)后的对象
// 参数1:
ClassLoader classLoader = JDKProxy.class.getClassLoader();
// 参数2:
Class[] interfaces = {UserService.class};
// 参数3:
// 参数3.1 将被增强的实现类实例 传入InvocationHandler
UserServiceImpl userService = new UserServiceImpl();
UserService u = (UserService) Proxy.newProxyInstance(classLoader, interfaces, new InvocationHandlerImpl(userService));
int res = u.queryCount(12, 12);
System.out.println("res: " + res + ", 被增强后执行最后结果");
}
}
// 参数3:
class InvocationHandlerImpl implements InvocationHandler {
// 1. 需要把被代理的对象,传递进来 通过有参构造函数传递
private Object obj;
public InvocationHandlerImpl(Object obj) {
this.obj = obj;
}
// 2. 做业务增强逻辑操作
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 方法之前
System.out.println("方法之前执行...." + method.getName() + " :传递的参数..." + Arrays.toString(args));
Object result = method.invoke(obj, args);
// 方法之后
System.out.println("方法之后执行...." + obj);
return result;
}
}
AspectJ + Spring AOP
Spring AOP 是一个简化版的 AOP 实现,并没有提供完整版的 AOP 功能。如 Spring AOP 仅支持执行公共(public)非静态方法的调用作为连接点,如果我们需要向受保护的(protected)或私有的(private)的方法进行增强,此时就需要使用功能更加全面的 AOP 框架
引入依赖包
spring-aop-5.2.6.RELEASE.jar
spring-aspects-5.2.6.RELEASE.jar
com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
com.springsource.net.sf.cglib-2.2.0.jar
com.springsource.org.aopalliance-1.0.0.jar
确定概念(主要理解"切点(PointCut)","通知(Advice)","切面(Aspect或Advisor)")
1. 通知(Advice)
我理解就是像是vue里面的路由守卫或者生命周期(只说用法相似 概念不一样哈 为了方便理解而已)的回调函数一样 理解成有6种回调方式
2. 切点(PointCut)
主要就是在众多"连接点"中选择哪些点来作为"切入点",可以理解为"天选之人"
3. 切面(Aspect)
由"通知"和"切点"组成一个完整的"切面",比如一个面可以服务于多个需要被增强的方法,只需要添加切点,修改通知来完善逻辑
4. 切入表达式(execution)
用于指定切入点关联的切入点表达式。
execution([权限修饰符] [返回值类型] [类的完全限定名] [方法名称]([参数列表])
返回值类型、方法名、参数列表是必须配置的选项,而其它参数则为可选配置项。
返回值类型:*表示可以为任何返回值。如果返回值为对象,则需指定全路径的类名。
类的完全限定名:指定包名 + 类名。
方法名:*代表所有方法,set* 代表以 set 开头的所有方法。
参数列表:(..)代表所有参数;(*)代表只有一个参数,参数类型为任意类型;(*,String)代表有两个参数,第一个参数可以为任何值,第二个为 String 类型的值。
AspectJ + Spring AOP(基于xml方式)
Spring 提供了基于 XML 的 AOP 支持,并提供了一个名为“aop”的命名空间,该命名空间提供了一个 <aop:config> 元素。
在 Spring 配置中,所有的切面信息(切面、切点、通知)都必须定义在 <aop:config> 元素中;
在 Spring 配置中,可以使用多个 <aop:config>。
每一个 <aop:config> 元素内可以包含 3 个子元素: pointcut、advisor 和 aspect ,这些子元素必须按照这个顺序进行声明。
1. 编写需要被增强类及方法
// 1. 可以称之"被增强类"
// 以下方法都可以称为术语中的”连接点“
public class UserDaoImpl implements UserDao {
@Override
public void regist() { // 连接点1
System.out.println("regist....");
}
@Override
public void logout() { // 连接点2
System.out.println("logout....");
}
@Override
public void login() { // 连接点3
System.out.println("login....");
}
}
2. 编写增强类
// 2. 可以称之"增强类" 用来对UserDaoImpl类进行增强
public class Aspect2UserDao {
// 3. 设置"通知"类型中的 "before" 前置通知
public void use2Before(){
System.out.println("before.....");
}
// 3. 设置"通知"类型中的 "after" 后置通知
public void use2After(){
System.out.println("after.....");
}
}
3. xml配置
3.1 引入aop名称空间
<!-- 4. 引入aop名称空间-->
<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.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
3.2 完整
<!-- 5. xml方式配置AOP-->
<!-- 5.1 IOC注入"增强类"和"被增强类"-->
<bean id="userDao" class="com.home.aop.dao.impl.UserDaoImpl"/>
<bean id="aspect2UserDao" class="com.home.aop.aspect.Aspect2UserDao"/>
<!-- 5.2 aop-config aop增强操作-->
<aop:config>
<!-- 5.2.1 定义切点-->
// id为当前切点起别名 方便后面通知时可以指定哪个切点
// expression指定哪个"连接点"成为"切点" 比如我们有3个连接点(login\logout\regist)结果让login成为切点
<aop:pointcut id="pcOne" expression="execution(* com.home.aop.dao.impl.UserDaoImpl.login(..))"/>
<!-- 5.2.2 定义切面-->
// ref引入我们的增强类作为"切面"
<aop:aspect ref="aspect2UserDao">
<!-- 5.2.3 采用何种通知(增强方式) 作用在何种切点上-->
// method:通知方式指定 在我们的增强类中的某个方法名称
// pointcut-ref 该通知方式 作用在哪个切点上面
<aop:before method="use2Before" pointcut-ref="pcOne"/>
</aop:aspect>
</aop:config>
AspectJ + Spring AOP(基于注解方式)
AspectJ 框架为 AOP 开发提供了一套 @AspectJ 注解。它允许我们直接在 Java 类中通过注解的方式对切面(Aspect)、切入点(Pointcut)和增强(Advice)进行定义
1. 注解种类
@Aspect 用于定义一个切面。
@Pointcut 用于定义一个切入点。
@Before 用于定义前置通知,相当于 BeforeAdvice。
@AfterReturning 用于定义后置通知,相当于 AfterReturningAdvice。
@Around 用于定义环绕通知,相当于 MethodInterceptor。
@AfterThrowing 用于定义抛出通知,相当于 ThrowAdvice。
@After 用于定义最终通知,不管是否异常,该通知都会执行。
2. 启用注解
我们在IOC模块中 使用两种方式来支持注解 同理若需要使用AspectJ提供的注解 也需要开启
2.1 xml配置开启 (注意引入context名称空间)
<!-- 开启注解扫描 -->
<context:component-scan base-package="com.home.aop"></context:component-scan>
<!--开启AspectJ 自动代理-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
2.2 配置类开启
@Configuration
@ComponentScan(basePackages = "com.home.aop") //注解扫描
@EnableAspectJAutoProxy //开启 AspectJ 的自动代理
public class AppConfig {
}
3. 完整
// DogDaoImpl.java
// 1. 创建需要"被增强"类
// 2. 通过注解成为IOC bean
@Component("dogDao")
public class DogDaoImpl implements DogDao {
@Override
public void run() {
System.out.println("i am a dog and now running");
}
}
// AspectAnno2DogDao.java
// 3. 创建"增强"类
// 4. 通过注解Component 成为IOC bean
// 5. 通过注解Aspect 定义我们的增强类为"切面"
@Component
@Aspect
public class AspectAnno2DogDao {
// 5. 定义切点
// 要求:返回值类型为 void,名称自定义,没有参数
@Pointcut("execution(* com.home.aop.dao.impl.DogDaoImpl.run(..))")
public void dogPointCut() {
}
// 6. 定义通知
@Before("dogPointCut()")
public void drink() {
System.out.println("dog run before need drink some water");
}
}
// test.java
@Test
public void testAnno(){
ApplicationContext context = new AnnotationConfigApplicationContext(AopConfig.class);
DogDao dogDao = context.getBean("dogDao", DogDao.class);
dogDao.run();
}
// console
dog run before need drink some water
i am a dog and now running
2022.05.24
Spring中的JdbcTemplate
JDBC 是 Java 提供的一种用于执行 SQL 语句的 API,可以对多种关系型数据库(例如 MySQL、Oracle 等)进行访问。
Spring 提供了一个 Spring JDBC 模块,它对 JDBC API 进行了封装,其的主要目的降低 JDBC API 的使用难度,以一种更直接、更简洁的方式使用 JDBC API。使用 Spring JDBC,开发人员只需要定义必要的参数、指定需要执行的 SQL 语句,即可轻松的进行 JDBC 编程,对数据库进行访问。
1. 依赖包
spring-jdbc-xxx.jar // Spring JDBC 的核心依赖包
spring-tx-xxx.jar // 用来处理事务和异常的依赖包
mysql-connector-java-8.0.16.jar // MySQL 提供的 JDBC 驱动包
2. jdbc配置文件
// jdbc.properties
prop.driverClassName=com.mysql.cj.jdbc.Driver
prop.url=jdbc:mysql://localhost:3306/xxx?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8
prop.userName=root
prop.password=*******
3. 引入context名称空间方便使用表达式 来绑定jdbc属性
<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 http://www.springframework.org/schema/beans/spring-context.xsd">
<context:property-placeholder location="classpath:jdbc.properties"/>
4. 使用spring 自带jdbc库定义数据库相关
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource" destroy-method="close">
<!-- 驱动名称-->
<property name="driverClassName" value="${prop.driverClassName}"/>
<!-- 数据库位置url-->
<property name="url" value="${prop.url}"/>
<!-- 用户名-->
<property name="username" value="${prop.userName}"/>
<!-- 密码-->
<property name="password" value="${prop.password}"/>
</bean>
5. 引入jdbcTemplate 相关Bean类 并向其注入mysql配置相关
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
6. beans.xml中开启IOC包扫描
<context:component-scan base-package="com.company.jdbc" />
7. 创建数据库及表
create database company;
CREATE TABLE
IF
NOT EXISTS `t_user` (
`id` INT UNSIGNED AUTO_INCREMENT,
`username` VARCHAR ( 50 ) NOT NULL,
`age` VARCHAR ( 10 ) NOT NULL,
`email` VARCHAR ( 100 ) NOT NULL,
PRIMARY KEY ( `id` )
) ENGINE = INNODB DEFAULT CHARSET = utf8;
8. 创建对应表实体类
// entity/User.java
public class User {
private int id;
private String username;
private int age;
private String email;
public User() {
}
public User(int id, String username, int age, String email) {
this.id = id;
this.username = username;
this.age = age;
this.email = email;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", age=" + age +
", email='" + email + '\'' +
'}';
}
}
8. 创建UserDao接口
// dao/UserDao.java
public interface UserDao {
// 新增用户
int addUser(User user);
// 编辑用户
int editUser(User user);
// 删除用户
int deleteUser(String id);
// 获取用户数量
int countUser();
// 批量新增用户
int[] addMuchUser(List<Object[]> muchUser);
// 批量编辑用户
int[] editMuchUser(List<Object[]> muchUser);
// 批量删除用户
int[] deleteMuchUser(List<Object[]> muchUser);
// 获取所有用户列表
List<User> findAllUser();
}
9. 实现UserDao
// dao/impl/UserDaoImpl
9.1 使用注解@Repository 自动装配类
9.2 使用注解@Resource 自动装配属性
@Repository
public class UserDaoImpl implements UserDao {
@Resource
private JdbcTemplate jdbcTemplate;
@Override
public int addUser(User user) {
String sql = "insert into t_user(username,age,email) values(?,?,?)";
// 参数1: sql
// 参数2: 可变的Object 数组格式
Object[] args = {user.getUsername(), user.getAge(), user.getEmail()};
// 返回数据库受影响的行数 int
return jdbcTemplate.update(sql, args);
}
@Override
public int editUser(User user) {
String sql = "update t_user set username=?,age=?,email=? where id=?";
// 参数1: sql
// 参数2: 可变的Object 数组格式
Object[] args = {user.getUsername(), user.getAge(), user.getEmail(), user.getId()};
// 返回数据库受影响的行数 int
return jdbcTemplate.update(sql, args);
}
@Override
public int deleteUser(String id) {
String sql = "delete from t_user where id = ?";
return jdbcTemplate.update(sql, id);
}
@Override
public int countUser() {
String sql = "select count(1) from t_user";
// 注意此处使用queryForObject()方法 返回某个值
// 参数1:sql
// 参数2:返回类型的class
Integer count = jdbcTemplate.queryForObject(sql, Integer.class);
return count;
}
@Override
public int[] addMuchUser(List<Object[]> muchUser) {
String sql = "insert into t_user(username,age,email) values(?,?,?)";
// 注意此处需要用到batchUpdate()方法 来执行批量操作
// 参数1:sql
// 参数2:List集合中包了个数组对象 对应多组数据对应的sql操作
return jdbcTemplate.batchUpdate(sql, muchUser);
}
@Override
public int[] editMuchUser(List<Object[]> muchUser) {
String sql = "update t_user set username=?, age=?, email=? where id=?";
// 注意此处需要用到batchUpdate()方法 来执行批量操作
// 参数1:sql
// 参数2:List集合中包了个数组对象 对应多组数据对应的sql操作
return jdbcTemplate.batchUpdate(sql, muchUser);
}
@Override
public int[] deleteMuchUser(List<Object[]> muchUser) {
String sql = "delete from t_user where id=?";
// 注意此处需要用到batchUpdate()方法 来执行批量操作
// 参数1:sql
// 参数2:List集合中包了个数组对象 对应多组数据对应的sql操作
return jdbcTemplate.batchUpdate(sql, muchUser);
}
@Override
public List<User> findAllUser() {
String sql = "select id,username,age,email from t_user";
// 注意此处需要用到query()方法 来执行批量操作
// 参数1:sql
// 参数2:RowMapper 是接口,针对返回不同类型数据,使用这个接口里面实现类完成数据封装
// 参数3:sql语句所需值
// return jdbcTemplate.queryForList(sql, User.class);
return jdbcTemplate.query(sql, new BeanPropertyRowMapper<User>(User.class));
}
}
10. 创建UserService接口
// service/UserService.java
public interface UserService {
// 新增用户
int addUserService(User user);
// 编辑用户
int editUserService(User user);
// 删除用户
int deleteUserService(String id);
// 获取用户数量
int countUserService();
// 批量新增用户
int[] addMuchUserService(List<Object[]> muchUser);
// 批量编辑用户
int[] editMuchUserService(List<Object[]> muchUser);
// 批量删除用户
int[] deleteMuchUserService(List<Object[]> muchUser);
// 获取所有用户列表
List<User> findAllUserService();
}
11. 实现UserService
// service/impl/UserServiceImpl
11.1 使用注解@Service 开启自动装配类
11.2 使用注解@Autowired 开启自动装配属性
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
@Override
public int addUserService(User user) {
return userDao.addUser(user);
}
@Override
public int editUserService(User user) {
return userDao.editUser(user);
}
@Override
public int deleteUserService(String id) {
return userDao.deleteUser(id);
}
@Override
public int countUserService() {
return userDao.countUser();
}
@Override
public int[] addMuchUserService(List<Object[]> muchUser) {
return userDao.addMuchUser(muchUser);
}
@Override
public int[] editMuchUserService(List<Object[]> muchUser) {
return userDao.editMuchUser(muchUser);
}
@Override
public int[] deleteMuchUserService(List<Object[]> muchUser) {
return userDao.deleteMuchUser(muchUser);
}
@Override
public List<User> findAllUserService() {
return userDao.findAllUser();
}
}
12. 创建Controller类
// controller/UserController.java
// 这里只是模仿三层架构调用模式 不规范的地方请谅解
12.1 使用注解@Controller 开启自动装配类
12.2 使用@Autowired 开启自动装配属性
@Controller("userController")
public class UserController {
@Autowired
private UserService userService;
public void addUserController(User user) {
int i = userService.addUserService(user);
System.out.println("添加了一个用户: " + user);
}
public void editUserController(User user) {
int i = userService.editUserService(user);
System.out.println("修改了一个用户:" + user);
}
public void deleteUserController(String id) {
int i = userService.deleteUserService(id);
System.out.println("删除了id为 " + id + " 的用户");
}
public void countUserController() {
int i = userService.countUserService();
System.out.println("当前用户总人数:" + i);
}
public void addMuchUserController(List<Object[]> muchUser) {
int[] ints = userService.addMuchUserService(muchUser);
System.out.println("批量添加了 " + muchUser + " 的用户");
}
public void editMuchUserController(List<Object[]> muchUser) {
int[] ints = userService.editMuchUserService(muchUser);
System.out.println("批量修改了 " + muchUser + " 的用户");
}
public void deleteMuchUserController(List<Object[]> muchUser) {
int[] ints = userService.deleteMuchUserService(muchUser);
System.out.println("批量删除了 " + muchUser + " 的用户");
}
public void findAllUserController() {
List<User> allUserService = userService.findAllUserService();
System.out.println("当前的所有用户: " + allUserService);
}
}
13. 创建测试类
// test/TestJdbc.java
public class TestJdbc {
@Test
public void test() {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
UserController userController = context.getBean("userController", UserController.class);
// 添加用户
User user1 = new User();
user1.setUsername("lidaye-newname");
user1.setAge(33);
user1.setEmail("lidaye-newname@qq.com");
userController.addUserController(user1);
// 修改用户
User user2 = new User();
user2.setId(1);
user2.setUsername("lidaye edit by other name");
user2.setAge(66);
user2.setEmail("editName@qq.com");
userController.editUserController(user2);
// 删除用户
userController.deleteUserController("10");
//返回当前用户数量
userController.countUserController();
// 批量新增用户
Object[] arg1 = {"lierye1", 21, "lierye1@qq.com"};
Object[] arg2 = {"lierye2", 22, "lierye2@qq.com"};
Object[] arg3 = {"lierye3", 23, "lierye3@qq.com"};
List<Object[]> listsAdd = new ArrayList<>();
listsAdd.add(arg1);
listsAdd.add(arg2);
listsAdd.add(arg3);
userController.addMuchUserController(listsAdd);
//批量修改用户
Object[] arg4 = {"lierye1-byEdit11", 121, "lierye1@qq.com-byEdit", 4};
Object[] arg5 = {"lierye2-byEdit11", 122, "lierye2@qq.com-byEdit", 5};
Object[] arg6 = {"lierye3-byEdit11", 123, "lierye3@qq.com-byEdit", 6};
List<Object[]> listsEdit = new ArrayList<>();
listsEdit.add(arg4);
listsEdit.add(arg5);
listsEdit.add(arg6);
userController.editMuchUserController(listsEdit);
// 批量删除
Object[] arg7 = {7};
Object[] arg8 = {8};
List<Object[]> listsDelete = new ArrayList<>();
listsDelete.add(arg7);
listsDelete.add(arg8);
userController.deleteMuchUserController(listsDelete);
// 查询当前所有用户
userController.findAllUserController();
}
}
14. console
添加了一个用户: User{id=0, username='lidaye-newname', age=33, email='lidaye-newname@qq.com'}
修改了一个用户:User{id=1, username='lidaye edit by other name', age=66, email='editName@qq.com'}
删除了id为 10 的用户
当前用户总人数:9
批量添加了 [[Ljava.lang.Object;@47caedad, [Ljava.lang.Object;@7139992f, [Ljava.lang.Object;@69504ae9] 的用户
批量修改了 [[Ljava.lang.Object;@1500b2f3, [Ljava.lang.Object;@7eecb5b8, [Ljava.lang.Object;@126253fd] 的用户
批量删除了 [[Ljava.lang.Object;@5c86a017, [Ljava.lang.Object;@5c7bfdc1] 的用户
当前的所有用户: [User{id=1, username='lidaye edit by other name', age=66, email='editName@qq.com'}, User{id=3, username='lidaye edit by other name', age=66, email='editName@qq.com'}, User{id=4, username='lierye1-byEdit11', age=121, email='lierye1@qq.com-byEdit'}, User{id=5, username='lierye2-byEdit11', age=122, email='lierye2@qq.com-byEdit'}, User{id=6, username='lierye3-byEdit11', age=123, email='lierye3@qq.com-byEdit'}, User{id=9, username='lierye2', age=22, email='lierye2@qq.com'}, User{id=11, username='lidaye-newname', age=33, email='lidaye-newname@qq.com'}, User{id=12, username='lierye1', age=21, email='lierye1@qq.com'}, User{id=13, username='lierye2', age=22, email='lierye2@qq.com'}, User{id=14, username='lierye3', age=23, email='lierye3@qq.com'}]
15. 关于junit控制台打印乱码
15.1 先检查当前test文件的file-encoding是否为utf-8
15.2 Run/Debug Configurations里面
设置Environment variables: -Dfile.encoding=utf-8
Spring中的事务操作
1. 什么是事务
事务(Transaction)是基于关系型数据库(RDBMS)的企业应用的重要组成部分。
事务具有 4 个特性:原子性、一致性、隔离性和持久性,简称为 ACID 特性。
原子性(Atomicity):一个事务是一个不可分割的工作单位,事务中包括的动作要么都做要么都不做。
一致性(Consistency):事务必须保证数据库从一个一致性状态变到另一个一致性状态,一致性和原子性是密切相关的。
隔离性(Isolation):一个事务的执行不能被其它事务干扰,即一个事务内部的操作及使用的数据对并发的其它事务是隔离的,并发执行的各个事务之间不能互相打扰。
持久性(Durability):持久性也称为永久性,指一个事务一旦提交,它对数据库中数据的改变就是永久性的,后面的其它操作和故障都不应该对其有任何影响。
2. Spring中的事务
Spring 并不会直接管理事务,而是通过事务管理器对事务进行管理的。(在 Spring 中提供了一个 org.springframework.transaction.PlatformTransactionManager 接口,这个接口被称为 Spring 的事务管理器)
3. Spring中操作事务方式
Spring中有2种方式操作事务:
1. 编程式事务管理
2. 声明式事务管理 (常用)
2.1 基于xml方式声明事务
2.2 基于注解方式声明事务
4. @Transactional参数分析 // 基于注解
4.1 propagation // 事务传播行为
多"事务方法"(指的是能让数据库表数据发生改变的方法,例如新增数据、删除数据、修改数据的方法。)直接进行调用,这个过程中事务,是如何进行管理的。而Spring定义了7种传播行为:
PROPAGATION_MANDATORY 支持当前事务,如果不存在当前事务,则引发异常。
PROPAGATION_NESTED 如果当前事务存在,则在嵌套事务中执行。
PROPAGATION_NEVER 不支持当前事务,如果当前事务存在,则引发异常。
PROPAGATION_NOT_SUPPORTED 不支持当前事务,始终以非事务方式执行。
PROPAGATION_REQUIRED 默认传播行为,如果存在当前事务,则当前方法就在当前事务中运行,如果不存在,则创建一个新的事务,并在这个新建的事务中运行。 // 默认
PROPAGATION_REQUIRES_NEW 创建新事务,如果已经存在事务则暂停当前事务。
PROPAGATION_SUPPORTS 支持当前事务,如果不存在事务,则以非事务方式执行。
@Transactional(propagation=Propagation.REQUIRED)
4.2 ioslation // 事务隔离级别
事务有特性成为隔离性,多事务操作之间不会产生影响,不考虑隔离性产生很多问题
4.2.1 脏读
一个未提交事务读取到另一个未提交事务的数据
4.2.2 不可重复读
一个未提交事务读取到另一个提交事务"修改的数据"
4.2.3 幻读
一个未提交事务读取到另一个提交事务"添加的数据"
ISOLATION_DEFAULT 使用后端数据库默认的隔离级别
// 脏读(存在) 不可重复读(存在) 幻读(存在)
ISOLATION_READ_UNCOMMITTED 允许读取尚未提交的更改,可能导致脏读、幻读和不可重复读
// 脏读(不存在) 不可重复读(存在) 幻读(存在)
ISOLATION_READ_COMMITTED Oracle 默认级别,允许读取已提交的并发事务,防止脏读,可能出现幻读和不可重复读
// 脏读(不存在) 不可重复读(不存在) 幻读(存在)
ISOLATION_REPEATABLE_READ MySQL 默认级别,多次读取相同字段的结果是一致的,防止脏读和不可重复读,可能出现幻读
// 脏读(不存在) 不可重复读(不存在) 幻读(不存在)
ISOLATION_SERIALIZABLE 完全服从 ACID 的隔离级别,防止脏读、不可重复读和幻读
@Transactional(isoLation=IsoLation.REPEATABLE_READ)
4.3 timeout // 超时时间
事务需要在一定时间内进行提交,如果不提交则进行回滚
默认值是-1 表示永不超时 单位是秒
4.4 readOnly // 是否只读
读:查询操作, 写:insert\update\delete
readOnly 默认值 false,表示可以查询,可以添加修改删除操作
设置 readOnly 值是 true,设置成 true 之后,只能查询
4.5 rollbackFor // 回滚
可以设置哪些异常即执行事务回滚
4.6 noRollbackFor // 不回滚
可以设置出现哪些异常不进行事务回滚
基于注解方式声明事务(不完整注解方法 注解类或者具体方法)
接着上面的jdbcTemplate操作
1. 修改下表结构 增加"money"字段
2. 则对应的user实体类也需要增加money属性及getter\setter\toString\constructor
3. 为了简单快速的测试 在dao层加入两个方法 一个作增加 一个作减少
// UserDao.java
// 增加账户金额
int addUserMoney(User user, BigDecimal money);
// 减少账户金额
int reduceUserMoney(User user, BigDecimal money);
// UserDaoImpl.java
@Override
public int addUserMoney(User user, BigDecimal money) {
String sql = "update t_user set money=money+? where id=?";
Object[] args = {money, user.getId()};
return jdbcTemplate.update(sql, args);
}
@Override
public int reduceUserMoney(User user, BigDecimal money) {
String sql = "update t_user set money=money-? where id=?";
Object[] args = {money, user.getId()};
return jdbcTemplate.update(sql, args);
}
4. 只在service层 测试 并加上@Transactional注解
// UserServiceImpl.java
@Override
@Transactional(readOnly = false, timeout = -1, propagation = Propagation.REQUIRED, isolation = Isolation.REPEATABLE_READ)
public int[] testTransaction() {
User A = new User();
A.setId(1);
User B = new User();
B.setId(2);
userDao.addUserMoney(A, BigDecimal.valueOf(500));
int x = 100 / 0; // 异常
userDao.reduceUserMoney(B, BigDecimal.valueOf(500));
return new int[0];
}
5. xml文件中添加aop\tx名称空间
// 由于事务是基于aop的 因此需要引入aop名称空间
// 上个例子已经引入tx依赖包了
<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"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
6. 开启事务管理器 由于使用jdbcTemplate则使用DataSourceTransactionManager
<!-- 6. 开启事务管理器-->
<bean id="transactionManage" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 6.1. 注入mysql数据源-->
<property name="dataSource" ref="dataSource"/>
</bean>
7. 配置tx开启事务注解
<tx:annotation-driven transaction-manager="transactionManage"/>
8. Test
@Test
public void testTransaction(){
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
UserService userService = context.getBean("userService", UserService.class);
userService.testTransaction();
}
基于xml配置文件声明事务
<!-- 9. 基于xml配置方式 开启事务-->
<!-- 9.1 配置通知-->
<tx:advice id="txAdvice">
<!-- 9.2 配置事务参数-->
<tx:attributes>
<!-- 9.3 指定在哪种规则的方法上添加事务-->
<tx:method name="testTransaction" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<!-- 9.4 配置切入点和切面-->
<aop:config>
<!-- 9.5 配置切入点-->
<aop:pointcut id="aopP" expression="execution(* com.company.jdbc.service.UserService.testTransaction(..))"/>
<!-- 9.6 配置切面-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="aopP"/>
</aop:config>
全面开启注解方式
由于在前面的例子中 有些配置写在xml中 有些又是使用注解 导致很混乱 因此下面使用配置类开启全面注解方式
// SpringConfig
@Configuration // 作为配置类,替代 xml 配置文件
@ComponentScan(basePackages = {"com.company.jdbc"}) // 扫描包位置 IOC
@EnableTransactionManagement // 开启事务
public class SpringConfig {
// 注入数据库的配置 使用durid依赖包
@Bean
public DruidDataSource getDruidDataSourceConfig() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/company?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8");
dataSource.setUsername("root");
dataSource.setPassword("xxx");
return dataSource; // 返回配置对象
}
// 注入JdbcTemplate
@Bean
public JdbcTemplate getJdbcTemplateConfig(DruidDataSource dataSource) { // 依赖上面的配置对象 因此参数就是该配置对象 会到IOC容器中找该对象
JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(dataSource);
return jdbcTemplate; // 返回jdbcTemplate配置对象
}
// 注入事务管理器
@Bean
public DataSourceTransactionManager getDataSourceTransactionManager(DruidDataSource dataSource) {
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
// 依赖上面的配置对象 因此参数就是该配置对象 会到IOC容器中找该对象 dataSource
dataSourceTransactionManager.setDataSource(dataSource);
return dataSourceTransactionManager;
}
}
// Service
@Override
@Transactional(readOnly = false, timeout = -1, propagation = Propagation.REQUIRED, isolation = Isolation.REPEATABLE_READ)
public int[] testTransaction() {
User A = new User();
A.setId(1);
User B = new User();
B.setId(2);
userDao.addUserMoney(A, BigDecimal.valueOf(500));
int x = 100 / 0;
userDao.reduceUserMoney(B, BigDecimal.valueOf(500));
return new int[0];
}
// Test
@Test
public void testTransaction(){
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
UserService testUser = context.getBean("testUser", UserService.class);
testUser.testTransaction();
}
结合其他理解
1. 关于数据库连接池
在上面的例子中,我们使用的Spring提供的jdbc库,在之前的JavaWeb学习中,我们使用的是druid-1.1.9.jar包来作的连接池
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
<!-- 驱动名称-->
<property name="driverClassName" value="${prop.driverClassName}"/>
<!-- 数据库位置url-->
<property name="url" value="${prop.url}"/>
<!-- 用户名-->
<property name="username" value="${prop.userName}"/>
<!-- 密码-->
<property name="password" value="${prop.password}"/>
</bean>
2. 关于jdbc使用
在上面中,我们直接使用的Spring的JdbcTemplate库,来操作,没有管数据库连接,关闭,异常等,非常方便。在JavaWeb的学习中,我们使用commons.dbutils-1.3.jar包来进行数据库的增删改查,同时针对数据库和实体类的注入绑定使用的是commons.beanutils-1.8.0.jar包来进行映射
// jdbc sql
new QueryRunner().update(connection, sql, args)
new QueryRunner().query(connection, sql, new BeanHandler<T>(type), args)
// beanutils
BeanUtils.populate(bean, value);
// JdbcTemplate
jdbcTemplate.query(sql, new BeanPropertyRowMapper<User>(User.class))
利用BeanPropertyRowMapper就可以完成数据集与bean的注入
3. 关于事务
在上面例子中,我们使用了注解方式和xml方式很方便的在Spring中开启事务管理,回想之前在JavaWeb的学习中,我们在代码里面写死了事务的判断,如下:
try{
// 1. 开启事务 不自动提交
// 2. dao逻辑1
// 3. 模拟异常
// 4. dao逻辑2
// 5. 提交事务
}catch{
// 6. 回滚事务
}
后面 配合ThreadLocal和Filter组件 来控制事务提交和回滚
2022.05.27
框架代码
整个 Spring5 框架的代码基于 Java8,运行时兼容 JDK9,许多不建议使用的类和方
法在代码库中删除
自带日志的封装Log4j2
Spring5 已经移除 Log4jConfigListener,官方建议使用 Log4j2,若需要使用Log4j需要降级处理
1. 引入依赖包
log4j-api-2.11.2.jar
log4j-core-2.11.2.jar
log4j-slf4j-impl-2.11.2.jar
slf4j-api-1.7.30.jar
2. 创建log4j2.xml
<?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>
private static final Logger logger = LoggerFactory.getLogger(Log4j2Test.class);
public static void main(String[] args) {
logger.error("测试logger");
}
@Nullable注解
(1)@Nullable 注解可以使用在方法上面,属性上面,参数上面,表示方法返回可以为空,属性值可以
为空,参数值可以为空
(2)注解用在方法上面,方法返回值可以为空
核心容器支持函数式风格 GenericApplicationContext
在之前的写法中 常常用new Xxx() 来显式创建类
// User.java
public class User {
private String name;
private String age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
public User() {
}
public User(String name, String age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age='" + age + '\'' +
'}';
}
}
// Test
@Test
public void test(){
// 函数式风格创建对象,交给 spring 进行管理
// 创建 GenericApplicationContext 对象
GenericApplicationContext genericApplicationContext = new GenericApplicationContext();
// 调用 context 的方法对象注册
genericApplicationContext.refresh();
genericApplicationContext.registerBean("userTest", User.class, () -> new User());
// 获取在 spring 注册的对象
User userTest = (User)genericApplicationContext.getBean("userTest");
userTest.setName("李大爷");
userTest.setAge("20");
System.out.println(userTest);
// User{name='李大爷', age='20'}
}
整合JUnit4
在之前的例子中 我们一直使用 junit-4.12.jar hamcrest-core-1.3.jar
JUnit4来作单元测试 但是特别是在作Spring的测试时,每次都需要显式的创建context对象,在显示的获取指定Bean来进行操作
@RunWith(SpringJUnit4ClassRunner.class) // 指定JUnit4测试框架
@ContextConfiguration("classpath:bean.xml") // 加载配置文件
public class TestJUnit4{
@Test
public void test(){
// ....
}
}
整合JUnit5
1. 引入JUnit5依赖
2. 方式1
@ExtendWith(SpringExtension.class)
@ContextConfiguration("classpath:bean.xml")
public class TestJUnit5{
@Test
public void test(){
// ....
}
}
3. 方式2
@SpringJUnitConfig(locations = "classpath:bean.xml")
public class TestJUnit5{
@Test
public void test(){
// ....
}
}