Spring5学习笔记(整理)
涉及的spring模块:
- spring-context(包含了aop、bean、core、expression)
- spring-aspects
- spring-orm(包含了spring-jdbc、spring-tx)
Spring框架概述

- Spring是轻量级的开源的JavaEE框架
- Spring可以解决企业应用开发的复杂性
- Spring有两个核心部分:IOC和AOP
- IOC(Inversion of Control),控制反转,把创建对象过程交给Spring进行管理
- AOP(Aspect Oriented Programming),面向切面编程,不修改源代码进行功能增强
- Spring特点:
- 方便解耦,简化开发
- AOP编程支持
- 方便程序测试
- 方便和其他框架整合
- 方便进行事务操作
- 降低API开发难度
Spring源码设计精妙,是提高Java编程技术的经典学习范例。
jar包和源码下载地址:在官网选择Spring Framework
,点击右上角GitHub图标,进入GitHub源码地址,在Access to Binaries
,进入Spring Framework Artifacts
wiki page,最下面Downloading a Distribution
,进入Spring仓库https://repo.spring.io
,选择Artifacts
-> release/org/springframework/spring/
即可找到对应版本
或者直接进入:https://repo.spring.io/release/org/springframework/spring/
入门案例
创建maven工程,引入spring-context
依赖(包含了其他依赖,core,beans,aop,expression,spring-jcl),spring使用了jcl日志门面,spring-jcl是spring的桥接器(桥接器是干啥的 忘了)
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.6.RELEASE</version> </dependency>
创建一个普通类,有一个普通方法
public class User { public void sayHello() { System.out.println("hello..."); } }
创建xml配置文件,使用配置文件创建对象
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!--使用配置文件 创建对象--> <bean id="user" class="org.example.beans.User"></bean> </beans>
获取对象:使用spring的context加载配置文件,并从context中获取这个对象
public class MainHello { public static void main(String[] args) { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("user.xml"); User user = context.getBean("user", User.class); System.out.println("user = " + user); user.sayHello(); } }
输出:
user = org.example.beans.User@367ffa75 hello...
IOC容器
概念和原理
IOC(Inversion of Control):控制反转,把对象创建和对象直接的调用过程交给Spring进行管理;使用IOC的目的是为了降低耦合度;入门案例就是IOC的实现
IOC底层原理:xml解析、工厂模式、反射,在a类中调用b类对象的方法时,a类通过调用工厂类的方法获得b类对象,进而调用方法,在工厂类中,通过xml解析获取到类的class,通过反射创建b类对象,降低了a类和b类的耦合,当b类的类路径改变时,工厂类一样可以正常返回b类对象
IOC的BeanFactory接口
IOC思想基于IOC容器完成,IOC容器底层就是对象工厂
Spring提供IOC容器的两种实现方式(两个接口)
- BeanFactory,IOC容器基本实现,是Spring内部的使用接口,不提供开发人员进行使用,加载配置文件时候不会创建对象,在获取对象(使用)才去创建对象
- ApplicationContext,BeanFactory接口的子接口,提供更多更强大的功能,一般由开发人员进行使用,加载配置文件时候就会把在配置文件对象进行创建
ApplicationContext有接口实现类
- FileSystemXmlApplicationContext
- ClassPathXmlApplicationContext
IOC操作: Bean管理(创建对象,注入属性)
Bean管理:指两个操作,Spring创建对象、Spring注入属性
Bean管理的两种方式:基于xml配置文件方式实现,基于注解方式实现
Bean的创建和属性注入:(xml方式)
- 创建对象:使用
bean
标签(注意有两种:普通bean和工厂bean) - 注入属性:
- 使用构造器或者set方法注入,直接写属性注入或者使用p名称空间(需要引入p名称空间)
- 注入null和特殊符号
- 注入内部bean:创建bean时,bean的某个属性是一个对象,直接创建为一个bean
- 注入外部bean:使用ref标签
- 级联赋值:注入外部bean,使用ref标签,可以直接设置ref对象的属性的值(需要有该对象的get方法)
- 注入集合,数组、List、Map、Set(可以使用util标签单独创建,被其他多个bean使用,需要引入util名称空间)
Bean的类型:Spring有两种bean,一种普通bean,另外一种工厂bean(实现接口FactoryBean)
- 普通bean:在配置文件中定义bean类型就是返回类型
- 工厂bean:在配置文件定义bean类型可以和返回类型不一样,实现FactoryBean接口,重写方法,定义返回类型,这里一般使用工厂模式加反射返回指定类型
Bean的作用域:
- 在Spring里面,设置创建bean实例是单实例还是多实例,默认情况下,bean是单实例对象
- 创建对象时使用
scope标签
配置,singleton,prototype,request,session - singleton,prototype区别:1,一个单例,一个多例,2,单例在加载配置文件时就已经创建好对象了,多例是在getBean时创建,每次创建都是一个新的对象
- request,session:会将对象放在对应域对象中
<?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:p="http://www.springframework.org/schema/p" xmlns:util="http://www.springframework.org/schema/util" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"> <!--基于xml方式 创建对象 id: 唯一标识 不能重复 class: 类的全限定名 创建对象时使用无参构造方法 --> <bean id="user" class="org.example.beans.User"></bean> <!--基于xml方式 注入属性(先创建对象 再注入属性) 使用DI: 依赖注入(注入属性) 是IOC的一种实现 使用有参构造或者set方法 --> <bean id="person" class="org.example.beans.Person"> <!--使用set方法注入 类必须有对应属性的set方法--> <property name="name" value="zhangsan"></property> <property name="age" value="10"/> <property name="male" value="0"/><!--0和1可以表示false和true--> </bean> <!--使用构造方法注入--> <bean id="student" class="org.example.beans.Student"> <constructor-arg name="name" value="lisi"></constructor-arg> <constructor-arg index="1" value="12"></constructor-arg> </bean> <!--使用p名称空间(set方法注入的简化)--> <bean id="personP" class="org.example.beans.Person" p:name="lisiP" p:age="10" p:male="true"> </bean> <!--注入null和特殊符号--> <bean id="personNull" class="org.example.beans.Person"> <!-- <property name="name"><null/></property>--> <property name="name"> <value><![CDATA[<<xxx]]></value><!--或者使用<>转义的<>--> </property> </bean> <!--注入其他bean 外部bean--> <bean id="userServiceImpl" class="org.example.service.UserServiceImpl"> <property name="userDao" ref="userDaoImpl"></property> </bean> <bean id="userDaoImpl" class="org.example.dao.UserDaoImpl"></bean> <!--级联赋值1 - 注入其他bean 内部bean 同时给其他bean的字段赋值--> <bean id="employee" class="org.example.beans.Employee"> <property name="name" value="zhansan"></property> <property name="department"><!--注入employee的时候直接new一个Department--> <bean id="department" class="org.example.beans.Department"> <property name="name" value="财务"></property> </bean> </property> </bean> <!--级联赋值2 - 注入其他bean 内部bean 同时给其他bean的字段赋值--> <bean id="employee1" class="org.example.beans.Employee"> <property name="name" value="lisi"></property> <property name="department" ref="department"></property> <property name="department.name" value="保安"></property><!--需要employee类有该属性的get方法--> </bean> <bean id="department" class="org.example.beans.Department"></bean> <!--注入集合属性--> <bean id="student1" class="org.example.beans.Student"> <property name="array"> <array> <value>s1</value> <value>s2</value> </array> </property> <property name="list"> <list> <value>v1</value> <value>v2</value> </list> </property> <property name="courseList"> <list> <ref bean="course1"></ref> <ref bean="course2"></ref> </list> </property> <property name="map"> <map> <entry key="k1" value="v1"/> <entry key="k2" value="v2"/> </map> </property> </bean> <bean id="course1" class="org.example.beans.Course"> <property name="name" value="Chinese"></property> </bean> <bean id="course2" class="org.example.beans.Course"> <property name="name" value="English"></property> </bean> <!--提取公共部分 使用util空间--> <util:list id="commonStringList"> <value>abc</value> <value>abc</value> </util:list> <bean id="student2" class="org.example.beans.Student"> <property name="list" ref="commonStringList"></property><!--引用公共list bean--> </bean> <!--创建工厂bean 该bean实际返回一个Course--> <bean id="myFactoryBean" class="org.example.beans.MyFactoryBean"/> <!--bean作用域 scope--> <bean id="studentScope1" class="org.example.beans.Student" scope="singleton"/> <bean id="studentScope2" class="org.example.beans.Student" scope="prototype"/> </beans>
Bean的生命周期:(7个步骤)
- 调用构造器创建bean实例
- 调用set方法设置bean的属性值和其他bean引用
- 调用后置处理器的
postProcessBeforeInitialization()
方法 - 调用初始化方法(
init-method
标签指定) - 调用后置处理器的
postProcessAfterInitialization()
方法 - 获取到bean实例进行使用
- 调用销毁方法(
destroy-method
标签指定),当ioc容器关闭时调用
注意:后置处理器是全局的,所有bean的创建都会调用,需要向ioc注入BeanPostProcessor接口
的实现类对象
<?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:p="http://www.springframework.org/schema/p" xmlns:util="http://www.springframework.org/schema/util" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"> <!--bean生命周期--> <bean id="beanLife" class="org.example.beans.BeanLife" init-method="initBean" destroy-method="destroyBean"> <constructor-arg name="name" value="first"></constructor-arg> <property name="otherField" value="set property"></property> </bean> <bean id="postProcessor" class="org.example.config.MyPostProcessor"/> </beans>
Bean的自动装配:(xml方式)
- 使用
autowire
标签指定装配规则(名称或者类型),选择byName
或byType
- 按名称,必须属性名称与id相同;按类型必须该类型的只有一个bean或者多个但其中一个
primary="true"
<?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:p="http://www.springframework.org/schema/p" xmlns:util="http://www.springframework.org/schema/util" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"> <!--bean生命周期--> <bean id="employeeAutowire" class="org.example.beans.Employee" autowire="byName"> <property name="name" value="one"/> </bean> <!--by name--> <bean id="department" class="org.example.beans.Department" primary="false"> <property name="name" value="first"/> </bean> <!--by type and is primary --> <bean id="departmentAutowire" class="org.example.beans.Department" primary="true"> <property name="name" value="second"/> </bean> </beans>
xml中引入外部properties配置文件:
- 需要引入context名称空间
- 使用
context:property-placeholder
引入配置文件
<?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:p="http://www.springframework.org/schema/p" xmlns:util="http://www.springframework.org/schema/util" 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/util http://www.springframework.org/schema/util/spring-util.xsd> http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!--直接配置连接池--> <!-- <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">--> <!-- <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>--> <!-- <property name="url" value="jdbc:mysql://localhost:3306/userDb"></property>--> <!-- <property name="username" value="root"></property>--> <!-- <property name="password" value="root"></property>--> <!-- </bean>--> <!--引入外部属性文件--> <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder> <!--配置连接池--> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="${jdbc.driverClass}"></property> <property name="url" value="${jdbc.url}"></property> <property name="username" value="${jdbc.username}"></property> <property name="password" value="${jdbc.password}"></property> </bean> </beans>
Bean的创建和属性注入:(注解方式)
- 使用注解的目的:简化xml配置
- Spring对创建对象提供4个注解:@Component,@Service,@Controller,@Repository,功能一样
- 分别代表:普通组件,业务层组件,控制层组件,持久层组件
- 需要开启注解扫描:
<context:component-scan base-package="org.example"></context:component-scan>
,默认扫描所有注解,多个包使用逗号隔开,也可以使用include或exclude过滤 - 属性注入:
- @Autowired:先根据类型自动装配,如果存在多个同类型的则使用属性名称匹配
- @Qualifier:跟@Autowired一起使用,指定名称进行注入
- @Resource:可以根据类型注入,也可以根据名称注入,默认先根据名称匹配,如果没有找到,再根据类型匹配,这个是javax扩展包中的注解,需要导入依赖
- @Value:注入普通类型属性,可以使用
${a.b:c}
取变量a.b的值,默认值为c,或者使用#{}
里面写表达式
完全注解开发,使用配置类替代xml配置文件中的组件扫描配置:
@Configuration //作为配置类 代替xml配置文件中的配置 @ComponentScan(basePackages = {"org.example.annotation"}) public class SpringConfig { }
启动方式修改为new AnnotationConfigApplicationContext(SpringConfig.class)
总结
- 创建对象:
- 使用xml配置文件
- 使用注解:@Component,@Service,@Controller,@Repository
- 注入属性:
- 使用xml配置文件
- 使用注解: @Autowired,@Qualifier,@Resource,@Value(可以使用#{},${})
- bean的类型:工厂bean,普通bean
- Bean的作用域:
scope
标签,singleton,prototype,request,session - bean生命周期:
BeanPostProcessor
接口注入后置处理器(在初始化方法的前后执行),每个bean可以设置初始化方法(bean初始化后执行,也即是执行完构造方法和一些属性set后)和销毁方法(容器关闭时执行)- 执行顺序:bean构造器,bean其他注入属性的方法,后置处理器before方法,bean自己指定的init方法,后置处理器的post方法,容器关闭时bean自己指定的销毁方法
- 如果使用完全注解开发,初始化方法和销毁方法在
@Bean
注解中可以指定
- IOC容器基本实现:BeanFactory,子接口:ApplicationContext,几个实现:
- FileSystemXmlApplicationContext
- ClassPathXmlApplicationContext
- AnnotationConfigApplicationContext
AOP
概念
- 面向切面编程,利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
- 通俗描述:不通过修改源代码方式,在主干功能里面添加新功能
- 术语:
- 连接点,类里面可以被增强的方法称为连接点
- 切入点,实际被增强的方法称为切入点
- 通知(增强),实际增强的逻辑部分称为通知,前置通知,后置通知,环绕通知,异常通知,最终通知
- 切面,将通知应用到切入点的过程称为切面
AOP底层使用动态代理,有接口情况,使用JDK动态代理,创建接口实现类代理对象,增强类的方法;没有接口情况,使用CGLIB(Code Generation Library)动态代理,创建子类的代理对象,增强类的方法。
JDK动态代理主要是使用Proxy.newInstance()创建代理类对象,每次调用代理类的对象的方法时,都会调用参数中InvocationHandler的invoke()方法,该方法的参数中包含被代理对象、调用的方法、方法的参数。
AOP操作(准备工作)
- Spring框架一般是基于AspectJ实现AOP操作,AspectJ不是Spring组成部分,独立AOP框架,一般把AspectJ和Spring框架一起使用,进行AOP操作。
引入依赖:
<!--aop依赖--> <!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.2.6.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/net.sourceforge.cglib/com.springsource.net.sf.cglib --> <dependency> <groupId>net.sourceforge.cglib</groupId> <artifactId>com.springsource.net.sf.cglib</artifactId> <version>2.2.0</version> </dependency> <!-- https://mvnrepository.com/artifact/org.aopalliance/com.springsource.org.aopalliance --> <dependency> <groupId>org.aopalliance</groupId> <artifactId>com.springsource.org.aopalliance</artifactId> <version>1.0.0</version> </dependency> <!-- https://mvnrepository.com/artifact/org.aspectj/com.springsource.org.aspectj.weaver --> <dependency> <groupId>org.aspectj</groupId> <artifactId>com.springsource.org.aspectj.weaver</artifactId> <version>1.6.8.RELEASE</version> </dependency>
基于AspectJ实现AOP操作:xml配置文件方式、注解方式
切入点表达式:
- 作用:知道对哪个类里面的哪个方法进行增强
- 语法结构:
execution([权限修饰符] [返回类型] [类全路径] [方法名称]([参数列表]))
举例1:对com.atguigu.dao.BookDao类里面的add进行增强 execution(* com.atguigu.dao.BookDao.add(..)) 举例2:对com.atguigu.dao.BookDao类里面的所有的方法进行增强 execution(* com.atguigu.dao.BookDao.* (..)) 举例3:对com.atguigu.dao包里面所有类,类里面所有方法进行增强 execution(* com.atguigu.dao.*.* (..))
使用AOP
- 对于任意类的任意方法,都可以配置一个增强类,对被增强的类的方法进行增强(前置通知
@Before
、后置通知@After
、环绕通知@Around
、异常通知@AfterThrowing
、返回通知@AfterReturning
) - 通常使用注解方式开发,增强类使用
@Aspect
,启动类开启自动代理@EnableAspectJAutoProxy
,增强类和被增强类都要放入ioc容器中 - 使用配置文件配置aop的话,不用配置开启自动代理
使用注解:
@Aspect //该类为增强类 @Order(1) //配置增强的优先级 越小优先级越高 @Component public class UserDaoImplAop { /** * 抽取切入点 后面的切入点就可以写这个抽取方法的方法名称() */ @Pointcut("execution(* org.example.dao.UserDaoImpl.updateUser(..))") public void pointDemo() { } //前置通知 @Before(value = "pointDemo()") public void before() { System.out.println("before..."); } //@After表示最终通知 方法有没有异常都会执行 方法执行完但在return前执行 @After(value = "execution(* org.example.annotation.dao.UserDaoImplx.add(..))") public void after() { System.out.println("after..."); } //后置通知 增强方法return后执行 @AfterReturning(value = "execution(* org.example.annotation.dao.UserDaoImplx.add(..))") public void afterReturning() { System.out.println("afterReturning..."); } //异常通知 方法出现异常后执行 @AfterThrowing(value = "execution(* org.example.annotation.dao.UserDaoImplx.add(..))") public void afterThrowing() { System.out.println("afterThrowing..."); } //环绕通知 在方法执行前后执行 @Around(value = "execution(* org.example.annotation.dao.UserDaoImplx.add(..))") public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { System.out.println("around before.."); proceedingJoinPoint.proceed(); System.out.println("around after.."); } }
使用配置文件:
<?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:p="http://www.springframework.org/schema/p" xmlns:util="http://www.springframework.org/schema/util" xmlns:aop="http://www.springframework.org/schema/aop" 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/util http://www.springframework.org/schema/util/spring-util.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!--测试使用配置文件配置切面--> <bean id="userDaoImpl" class="org.example.dao.UserDaoImpl"/> <bean id="userDaoImplAop" class="org.example.aop.UserDaoImplProxy2"/> <aop:config> <!--这个相当于抽取的切入点--> <aop:pointcut id="pointDemo" expression="execution(* org.example.dao.UserDaoImpl.updateUser(..))"/> <!--配置切面 使用哪个增强类--> <aop:aspect ref="userDaoImplAop"> <!--配置增强到具体的方法上--> <aop:before method="before" pointcut-ref="pointDemo"></aop:before> <!--这里的切入点没有抽取--> <aop:after method="after" pointcut="execution(* org.example.dao.UserDaoImpl.updateUser(..))"></aop:after> </aop:aspect> </aop:config> </beans>
总结
- 启动类开启自动代理
@EnableAspectJAutoProxy
- 切入点表达式的写法
- 增强类上使用注解
@Aspect
,增强方法上使用注解(@Before
、@After
、@Around
、@AfterThrowing
、@AfterReturning
) - 增强类和被增强类都要放入ioc容器中
- 目的是在被执行的方法执行前后、返回前后、异常时增强一些逻辑
使用Spring JdbcTemplate操作数据库(增删改查)
Spring框架对jdbc进行的封装,使用JdbcTemplate方便实现对数据库的操作。
需要引入的数据库操作相关依赖:
<!--整合其他mybatis等需要的依赖 包含了spring-jdbc spring-tx spring-beans spring-core --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-orm</artifactId> </dependency> <!--mysql jdbc--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <!--druid连接池--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> </dependency>
使用:在创建JdbcTemplate时,传入一个DataSource对象即可,比如druid的连接池,然后就可以JdbcTemplate进行数据库操作了
@Component public class UserDaoImp implements UserDao { // @Autowired @Resource private JdbcTemplate jdbcTemplate; //插入数据 @Override public void add(User user) { String sql = "insert into book values (?,?,?)"; Object[] args = {user.getId(), user.getName(), user.getStatus()}; int add = jdbcTemplate.update(sql, args); System.out.println("add = " + add); } //更新数据 @Override public void update(User user) { String sql = "update book set name=?, status=? where id=?"; Object[] args = {user.getName(), user.getStatus(), user.getId()}; int update = jdbcTemplate.update(sql, args); System.out.println("update = " + update); } //删除数据 @Override public void deleteById(String id) { String sql = "delete from book where id=?"; int delete = jdbcTemplate.update(sql, id); System.out.println("delete = " + delete); } //查询总数 @Override public long count() { String sql = "select count(*) from book"; Long aLong = jdbcTemplate.queryForObject(sql, Long.class); System.out.println("aLong = " + aLong); return aLong; } //查询对象 //BeanPropertyRowMapper 这个默认mapper是怎么匹配bean字段和表字段的? @Override public User selectById(String id) { String sql = "select * from book where id=?"; User user = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(User.class), id); System.out.println("user = " + user); return user; } //查询多个对象 @Override public List<User> selectAll() { String sql = "select * from book"; List<User> userList = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(User.class)); System.out.println("userList = " + userList); return userList; } //批量插入与单条插入类似 只是多次执行单条语句 @Override public int[] batchAdd(List<User> userList) { String sql = "insert into book values (?,?,?)"; List<Object[]> args = userList.stream().map(x -> new Object[]{x.getId(), x.getName(), x.getStatus()}) .collect(Collectors.toList()); int[] ints = jdbcTemplate.batchUpdate(sql, args); System.out.println("Arrays.toString(ints) = " + Arrays.toString(ints)); return ints; } //批量更新 与单条更新类似 只是多次执行单条语句 @Override public int[] batchUpdate(List<User> userList) { String sql = "update book set name=?, status=? where id=?"; List<Object[]> args = userList.stream().map(x -> new Object[]{x.getName(), x.getStatus(), x.getId()}) .collect(Collectors.toList()); int[] ints = jdbcTemplate.batchUpdate(sql, args); System.out.println("Arrays.toString(ints) = " + Arrays.toString(ints)); return ints; } //批量删除 与单条删除类似 只是多次执行单条语句 @Override public int[] batchDelete(List<String> idList) { String sql = "delete from book where id=?"; List<Object[]> args = idList.stream().map(x -> new Object[]{x}) .collect(Collectors.toList()); int[] ints = jdbcTemplate.batchUpdate(sql, args); System.out.println("Arrays.toString(ints) = " + Arrays.toString(ints)); return ints; } }
Spring事务管理
事务概念
- 事务是数据库操作最基本单元,逻辑上一组操作,要么都成功,如果有一个失败所有操作都失败
- 四个特性(ACID):(1)原子性(2)一致性(3)隔离性(4)持久性
- 事务的隔离级别:读未提交、读已提交、可重复读、串行化。后面三个分别解决了脏读、不可重复读、幻读的问题。MySQL默认是可重复级别
- 脏读:未提交的事务a读取了另一个未提交事务b插入的数据,b事务回滚了,导致a事务读到了脏数据,是一个很严重的问题
- 不可重复读:未提交的事务a读取到了另一个已提交事务b修改的数据(在b提交前读到了),a事务在b事务提交前后读取到的数据不一样,是一个现象
- 幻读:未提交的事务a读取到了另一个已提交事务b插入的数据(在b提交前没读到),a事务在b事务提交后读到了
隔离级别、解决的问题 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
读未提交 Read Uncommitted | n | n | n |
读已提交 Read Committed | y | n | n |
可重复读 Read Repeatly | y | y | n |
串行化 Serialization | y | y | y |
Spring事务操作
- Spring事务管理是基于AOP实现的
- 事务一般添加到JavaEE三层结构里面Service层(业务逻辑层)
- 管理事务的两种方式:编程式事务管理和声明式事务管理(一般使用声明式)
- 声明式事务管理:(1)基于注解方式(2)基于xml配置文件方式。
- Spring事务管理API,提供一个接口
PlatformTransactionManager
,代表事务管理器,针对不同的框架提供不同的实现类(如Jpa,Hibernate,使用jdbc则用DataSourceTransactionManager)
声明式事务管理:(使用注解配置)
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" xmlns:p="http://www.springframework.org/schema/p" xmlns:util="http://www.springframework.org/schema/util" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" 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/util http://www.springframework.org/schema/util/spring-util.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 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!--开启注解扫描 使用注解 --> <context:component-scan base-package="org.example"></context:component-scan> <!--引入外部属性文件--> <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder> <!--配置连接池--> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="${jdbc.driverClass}"></property> <property name="url" value="${jdbc.url}"></property> <property name="username" value="${jdbc.username}"></property> <property name="password" value="${jdbc.password}"></property> </bean> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <!--使用构造器和set注入都可以 也有set方法--> <constructor-arg name="dataSource" ref="dataSource"></constructor-arg> </bean> <!-- 测试事务 --> <!--配置事务管理器 这里配置为jdbc的事务管理器DataSourceTransactionManager 它的顶级接口有其他各个框架的事务管理器实现--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!--使用构造器和set注入都可以 也有set方法--> <constructor-arg name="dataSource" ref="dataSource"></constructor-arg> </bean> <!--配置注解驱动的事务管理器 --> <!-- <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>--> </beans>
使用注解@Transactional,可以加在类上面,也可以加在方法上面
//测试事务 //如果方法内发生异常 所有操作都会回滚 //如果放在类上 表示所以方法都是事务方法 @Transactional(propagation = Propagation.REQUIRED, //有7种,常用的有两种REQUIRED和REQUIRED_NEW,考虑a方面调用b方法,对于前者,如果a有事务,调用b后使用a里面的事务,如果a没有事务,创建新事务;对于后者,不管a有没有事务都创建新事务 isolation = Isolation.REPEATABLE_READ, //事务隔离级别,有4种,对应三个读问题(脏读,不可重复读,幻读) timeout = -1, //超时时间,规定事务提交时间,未提交则回滚 readOnly = false, //是否只读,默认false,可以读可以写,如果只读则事务内只读数据 rollbackFor = {}, //回滚,设置出现哪些异常进行回滚 rollbackForClassName = {}, noRollbackFor = {},//不回滚,设置出现那里写异常不回滚 noRollbackForClassName = {}) public void testTx(String id1, String id2) { userDao.deleteById(id1); int i = 1 / 0; userDao.deleteById(id2); }
声明式事务管理:(使用注解xml配置)
aop的expression需要依赖包:
<!--xml方式配置事务需要aop的这个依赖 使用注解就不需要(aop中的expression需要依赖) Caused by: java.lang.ClassNotFoundException: org.aspectj.weaver.reflect --> <dependency> <groupId>org.aspectj</groupId> <artifactId>com.springsource.org.aspectj.weaver</artifactId> </dependency>
配置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:p="http://www.springframework.org/schema/p" xmlns:util="http://www.springframework.org/schema/util" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" 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/util http://www.springframework.org/schema/util/spring-util.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 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!--开启注解扫描 使用注解 --> <!-- <context:component-scan base-package="org.example"></context:component-scan>--> <!--不与完全注解开发的包内bean重复--> <context:component-scan base-package="org.example.dao"></context:component-scan> <context:component-scan base-package="org.example.service"></context:component-scan> <!--引入外部属性文件--> <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder> <!--配置连接池--> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="${jdbc.driverClass}"></property> <property name="url" value="${jdbc.url}"></property> <property name="username" value="${jdbc.username}"></property> <property name="password" value="${jdbc.password}"></property> </bean> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <!--使用构造器和set注入都可以 也有set方法--> <constructor-arg name="dataSource" ref="dataSource"></constructor-arg> </bean> <!-- 测试事务 使用配置文件配置事务 --> <!--1 配置事务管理器 这里配置为jdbc的事务管理器DataSourceTransactionManager 它的顶级接口有其他各个框架的事务管理器实现--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!--使用构造器和set注入都可以 也有set方法--> <constructor-arg name="dataSource" ref="dataSource"></constructor-arg> </bean> <!--配置使用什么事务管理器 如果用xml方式配置事务 这里就不需要配置了--> <!-- <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>--> <!--2 配置事务通知(增强部分)--> <tx:advice id="myTxAdvice"> <!--配置事务的参数--> <tx:attributes> <!--准确配置哪个方法上面添加事务(只是配置名字)--> <tx:method name="testTx2" isolation="REPEATABLE_READ" propagation="REQUIRED"></tx:method><!--其他属性配置--> <!--也可以使用模糊匹配的方式配置--> <!-- <tx:method name="testTx*"/>--> </tx:attributes> </tx:advice> <!--3 配置切入点和切面(配置哪个地方需要增强 切面是指增强的过程 也即是将通知配置到切入点的过程)--> <aop:config> <!--配置切入点 可以将切入点抽出来作为一个ref进行配置 expression这个需要com.springsource.org.aspectj.weaver依赖!!! --> <aop:pointcut id="pointRef" expression="execution(* org.example.service.UserService.testTx2(..)))"/> <!--配置切面--> <aop:advisor advice-ref="myTxAdvice" pointcut-ref="pointRef"></aop:advisor> <!--配置切面 这里直接写了切入点--> <!-- <aop:advisor advice-ref="myTxAdvice" pointcut="execution(* org.example.service.UserService.testTx(..)))"></aop:advisor>--> </aop:config> </beans>
事务操作(完全使用注解配置,将上面注解实现时xml里面的内容放在配置类里面)
@Configuration //配置类 @ComponentScan(basePackages = {"org.example"}) //包扫描 @EnableTransactionManagement(mode = AdviceMode.PROXY) //开启事务管理 Proxy是代理模式(实现有jdk和cglib) 还有一个aspectj模式(?) public class TxConfig { //注入dataSource @Bean //方法名为bean的名称 public DataSource dataSource() { DruidDataSource dataSource = new DruidDataSource(); dataSource.setDriverClassName("com.mysql.jdbc.Driver"); dataSource.setUrl("jdbc:mysql://localhost:3306/user_db_spring"); dataSource.setUsername("root"); dataSource.setPassword("999999"); return dataSource; } //注入JdbcTemplate @Bean public JdbcTemplate jdbcTemplate(@Autowired @Qualifier("dataSource") DataSource dataSource) { //autowired自动注入 return new JdbcTemplate(dataSource); } //注入事务管理器transactionManager @Bean public TransactionManager transactionManager(DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } }
总结
- 开启事务管理,注入事务管理对象,使用
@Transactional
注解
Spring5新特性
- 整个Spring5框架的代码基于Java8,运行时兼容JDK9,许多不建议使用的类和方法在代码库中删除
- Spring5框架自带了通用的日志封装,移除Log4jConfigListener,官方建议使用Log4j2
- 日志配置文件:log4j2.xml
- 使用sl4j日志门面,需要使用log4j2的适配器
private static final Logger log = LoggerFactory.getLogger(MainApplication.class);
<!--log4j2适配slf4j的适配器(log4j的日志打到slf4j里面) 这里是log4j的2.17.1版本log4j2 包含了log4j-api和log4j-core slf4j-api = slf4j + log4j2 --> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-slf4j-impl</artifactId> <version>2.17.1</version> </dependency>
- Spring5框架核心容器支持
@Nullable
,@NonNull
注解,可以使用在方法、属性、参数上面,表示可以为空、不为空(只是标注,并不影响编译和运行) - Spring5核心容器支持函数式风格GenericApplicationContext,将对象注入context
@Test public void testGenericApplicationContext() { //1 创建GenericApplicationContext对象 GenericApplicationContext context = new GenericApplicationContext(); //2 调用context的方法对象注册 context.refresh(); context.registerBean(User.class, () -> new User("1", "a", "1")); //没有指定beanName那么就是类的全限定名名称 context.registerBean(User.class, () -> new User("2", "a", "1")); //覆盖了上面的bean context.registerBean("user",User.class, () -> new User("3", "a", "1")); //新注册的 //3 获取在spring注册的对象 String[] names = context.getBeanNamesForType(User.class); System.out.println("names = " + Arrays.toString(names)); //names = [org.example.beans.User, user] int count = context.getBeanDefinitionCount(); //获取bean的数量 System.out.println("count = " + count); //2 //get bean User user = (User)context.getBean("org.example.beans.User"); System.out.println("user = " + user); //user = User(id=2, name=a, status=1) User user1 = context.getBean("user", User.class); System.out.println("user1 = " + user1); //user1 = User(id=3, name=a, status=1) System.out.println("user == user1 = " + (user == user1)); //false }
- Spring5支持整合JUnit5
使用junit4:需要引入junit4依赖
<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13.2</version> <!-- <scope>test</scope>--> </dependency>
@RunWith(SpringJUnit4ClassRunner.class) //spring使用junit4框架测试 @ContextConfiguration(value = "classpath:tx.xml") //代替了使用xml文件获取context //@ContextConfiguration(classes = TxConfig.class) //代替了使用配置类获取context public class JUnit4Test { @Autowired UserService userService; //直接注入 @Test public void testTx() { userService.truncateTable("book"); userService.addUser(new User("1001", "a", "a")); userService.addUser(new User("1002", "b", "b")); userService.testTx("1001", "1002"); } }
使用JUnit5:需要引入junit5的依赖
<dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter</artifactId> <version>5.9.0-M1</version> <!--在src下也使用测试 去掉test scope--> <!-- <scope>test</scope>--> </dependency>
//@ExtendWith(SpringExtension.class) //@ContextConfiguration(value = "classpath:tx.xml") //代替了使用xml文件获取context //@ContextConfiguration(classes = TxConfig.class) //代替了使用配置类获取context @SpringJUnitConfig(locations = "classpath:tx.xml") //复合注解 相当于上面两个 //@SpringJUnitConfig(classes = TxConfig.class) //复合注解 相当于上面两个 public class JUnit5Test { @Autowired UserService userService; //直接注入 @Test //使用junit5 void testTx() { //可以不写public userService.truncateTable("book"); userService.addUser(new User("1001", "a", "a")); userService.addUser(new User("1002", "b", "b")); userService.testTx("1001", "1002"); } }
Spring5框架新功能(Webflux)
需要基础知识:SpringMVC,SpringBoot,Maven,Java8新特性
本文来自博客园,作者:Bingmous,转载请注明原文链接:https://www.cnblogs.com/bingmous/p/15643689.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?