本篇文章是学习完b站尚硅谷Spring5所做的笔记,希望对大家有所帮助!
一、Spring框架概述
-
Spring 是轻量级的开源的 JavaEE 框架。
-
Spring 可以解决企业应用开发的复杂。
-
Spring 有两个核心部分:IOC 和 Aop 。
(1)IOC:控制反转,把创建对象过程交给 Spring 进行管理
(2)Aop:面向切面,不修改源代码进行功能增强
-
Spring 特点 :
(1)方便解耦,简化开发
(2)Aop 编程支持
(3)方便程序测试
(4)方便和其他框架进行整合
(5)方便进行事务操作
(6)降低 API 开发难度
二、IOC容器
1. IOC概念和原理
(1)概念:
-
控制反转,把对象创建和对象的调用过程交给spring进行管理。
-
目的:降低耦合度。
-
底层原理:xml,反射,工厂模式
-
Spring提供IOC容器两种实现方式(两个接口)
-
BeanFactory:Spring内部使用的接口,不提倡开发人员使用。特点:加载配置文件时不会创建对象,获取对象时才会创建对象。
-
**ApplicationContext:**BeanFactory的子接口,提供了更多更强大的功能,一般由开发人员使用。特点:加载配置文件时会把配置文件里的对象进行创建。
-
ApplicationContext两个常用实现类:
- FileSystemXmlApplicationContext:绝对路径,从盘符开始算起
- ClassPathXmlApplicationContext:相对路径,从src开始算起
-
什么是Bean管理?Bean管理是指两个操作:Spring创建对象 和 Spring注入属性
Bean管理有两种操作方式:基于xml配置文件方式实现 和 基于注解方式实现
2. IOC操作Bean管理(基于xml)
xml实现Bean管理:
(1)基于xml方式创建对象:
- 在Spring配置文件中使用bean标签来创建对象
- bean标签有很多属性,常用属性:
- id:唯一标识
- class:类路径
- 创建对象时,默认执行无参构造函数
(2)基于xml方式注入属性:
第一种方法:使用set方法进行注入:
首先先为类的属性提供set方法:
public class User {
private String userName;
private String userAge;
public void setUserName(String userName) {
this.userName = userName;
}
public void setUserAge(String userAge) {
this.userAge = userAge;
}
public String getUserName() {
return userName;
}
public String getUserAge() {
return userAge;
}
}
然后在xml配置文件中通过property标签进行属性注入
<!--配置User对象-->
<bean id="user" class="com.oymn.spring5.User">
<property name="userName" value="haha"></property>
<property name="userAge" value="18"></property>
</bean>
这样就完成了
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean1.xml");
User user = applicationContext.getBean("user", User.class);
System.out.println(user.getUserName() + " " + user.getUserAge());
第二种方法:使用有参构造函数进行注入
首先提供有参构造方法
public class User {
private String userName;
private String userAge;
public User(String userName, String userAge){
this.userName = userName;
this.userAge = userAge;
}
}
然后再xml配置文件中通过constructor-arg标签进行属性注入
<!--配置User对象-->
<bean id="user" class="com.oymn.spring5.User">
<constructor-arg name="userName" value="haha"></constructor-arg>
<constructor-arg name="userAge" value="18"></constructor-arg>
</bean>
第三种方法:p名称空间注入(了解即可)
首先在xml配置文件中添加p名称空间,并且在bean标签中进行操作
然后提供set方法
public class User {
private String userName;
private String userAge;
public User() {
}
public void setUserName(String userName) {
this.userName = userName;
}
public void setUserAge(String userAge) {
this.userAge = userAge;
}
}
(3)xml注入其他属性
- null值
<!--配置User对象-->
<bean id="user" class="com.oymn.spring5.User">
<property name="userName"> <null/> </property>
</bean>
-
属性值包含特殊符号
假设现在userName属性需要赋值为 < haha >
如果像上面那样直接在value中声明的话会报错,因为包含特殊符号 <>
需要通过
<![CDATA[值]]>
来表示 -
注入属性——外部bean
有两个类:UserService和UserDaoImpl,其中UserDaoImpl实现UserDao接口
public class UserService { private UserDao userDao; public void setUserDao(UserDao userDao){ this.userDao = userDao; } public void add(){ System.out.println("add"); } }
通过 ref 来指定创建userDaoImpl
<bean id="userDaoImpl" class="com.oymn.spring5.UserDaoImpl"></bean> <bean id="userService" class="com.oymn.spring5.UserService"> <property name="userDao" ref="userDaoImpl"></property> </bean>
-
注入属性——内部bean
不通过ref属性,而是通过嵌套一个bean标签实现
<!--内部 bean-->
<bean id="emp" class="com.atguigu.spring5.bean.Emp">
<!--设置两个普通属性-->
<property name="ename" value="lucy"></property>
<property name="gender" value="女"></property>
<!--设置对象类型属性-->
<property name="dept">
<bean id="dept" class="com.atguigu.spring5.bean.Dept">
<property name="dname" value="安保部"></property>
</bean>
</property>
</bean>
-
注入属性——级联赋值
写法一:也就是上面所说的外部bean,通过ref属性来获取外部bean
写法二:
emp类中有ename和dept两个属性,其中dept有dname属性,写法二需要emp提供dept属性的get方法。
<!--级联赋值--> <bean id="emp" class="com.atguigu.spring5.bean.Emp"> <!--设置两个普通属性--> <property name="ename" value="lucy"></property> <property name="gender" value="女"></property> <!--写法一--> <property name="dept" ref="dept"></property> <!--写法二--> <property name="dept.dname" value="技术部"></property> </bean> <bean id="dept" class="com.atguigu.spring5.bean.Dept"> <property name="dname" value="财务部"></property> </bean>
-
注入集合属性(数组,List,Map)
假设有一个Stu类
public class Stu {
private String[] courses;
private List<String> list;
private Map<String,String> map;
private Set<String> set;
public void setCourses(String[] courses) {
this.courses = courses;
}
public void setList(List<String> list) {
this.list = list;
}
public void setMap(Map<String, String> map) {
this.map = map;
}
public void setSet(Set<String> set) {
this.set = set;
}
}
在xml配置文件中对这些集合属性进行注入
<bean id="stu" class="com.oymn.spring5.Stu">
<!--数组类型属性注入-->
<property name="courses">
<array>
<value>java课程</value>
<value>数据库课程</value>
</array>
</property>
<!--List类型属性注入-->
<property name="list">
<list>
<value>张三</value>
<value>李四</value>
</list>
</property>
<!--Map类型属性注入-->
<property name="map">
<map>
<entry key="JAVA" value="java"></entry>
<entry key="PHP" value="php"></entry>
</map>
</property>
<!--Set类型属性注入-->
<property name="set">
<set>
<value>Mysql</value>
<value>Redis</value>
</set>
</property>
</bean>
-
上面的集合值都是字符串,如果是对象的话,如下:
写法: 集合+外部bean
<!--创建多个 course 对象-->
<bean id="course1" class="com.atguigu.spring5.collectiontype.Course">
<property name="cname" value="Spring5 框架"></property>
</bean>
<bean id="course2" class="com.atguigu.spring5.collectiontype.Course">
<property name="cname" value="MyBatis 框架"></property>
</bean>
<!--注入 list 集合类型,值是对象-->
<property name="courseList">
<list>
<ref bean="course1"></ref>
<ref bean="course2"></ref>
</list>
</property>
-
把集合注入部分提取出来
使用 util 标签,这样不同的bean都可以使用相同的集合注入部分了。
<!--将集合注入部分提取出来--> <util:list id="booklist"> <value>易筋经</value> <value>九阳神功</value> </util:list> <bean id="book" class="com.oymn.spring5.Book"> <property name="list" ref="booklist"></property> </bean>
-
FactoryBean
Spring有两种Bean,一种是普通Bean,另一种是工厂Bean(FactoryBean)
这块看不太懂,不知道有啥用,先放着。
Bean的作用域:
- 在Spring中,默认情况下bean是单实例对象
执行结果是相同的:
- 通过 bean标签的scope属性 来设置单实例还是多实例。
Scope属性值:
- **singleton:**默认值,表示单实例对象。加载配置文件时就会创建单实例对象。
- prototype:表示多实例对象。不是在加载配置文件时创建对象,在调用getBean方法时创建多实例对象。
执行结果不同了:
Bean的生命周期:
- bean的生命周期:
(1)通过构造器创建 bean 实例(无参数构造)
(2)为 bean 的属性设置值和对其他 bean 引用(调用 set 方法)
(3)把 bean 实例传递 bean 后置处理器的方法 postProcessBeforeInitialization
(4)调用 bean 的初始化的方法(需要进行配置初始化的方法)
(5)把 bean 实例传递 bean 后置处理器的方法 postProcessAfterInitialization
(6)bean 可以使用了(对象获取到了)
(7)当容器关闭时候,调用 bean 的销毁的方法(需要进行配置销毁的方法)
- 演示bean的生命周期
public class Orders {
private String orderName;
public Orders() {
System.out.println("第一步:执行无参构造方法创建bean实例");
}
public void setOrderName(String orderName) {
this.orderName = orderName;
System.out.println("第二步:调用set方法设置属性值");
}
//初始化方法
public void initMethod(){
System.out.println("第四步:执行初始化方法");
}
//销毁方法
public void destroyMethod(){
System.out.println("第七步:执行销毁方法");
}
}
//实现后置处理器,需要实现BeanPostProcessor接口
public class MyBeanPost implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("第三步:将bean实例传递给bean后置处理器的postProcessBeforeInitialization方法");
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("第五步:将bean实例传递给bean后置处理器的postProcessAfterInitialization方法");
return bean;
}
}
<bean id="orders" class="com.oymn.spring5.Orders" init-method="initMethod" destroy-method="destroyMethod">
<property name="orderName" value="hahah"></property>
</bean>
<!--配置bean后置处理器,这样配置后整个xml里面的bean用的都是这个后置处理器-->
<bean id="myBeanPost" class="com.oymn.spring5.MyBeanPost"></bean>
@Test
public void testOrders(){
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
Orders orders = context.getBean("orders", Orders.class);
System.out.println("第六步:获取bean实例对象");
System.out.println(orders);
//手动让bean实例销毁
context.close();
}
执行结果:
xml自动装配:
-
根据指定的装配规则(属性名称或者属性类型),Spring自动将匹配的属性值进行注入
-
根据属性名称自动装配:要求 emp中属性的名称dept 和 bean标签的id值dept 一样,才能识别
-
<!--指定autowire属性值为byName--> <bean id="emp" class="com.oymn.spring5.Emp" autowire="byName"></bean> <bean id="dept" class="com.oymn.spring5.Dept"></bean>
-
根据属性类型自动装配:要求同一个xml文件中不能有两个相同类型的bean,否则无法识别是哪一个
-
<!--指定autowire属性值为byType--> <bean id="emp" class="com.oymn.spring5.Emp" autowire="byType"></bean> <bean id="dept" class="com.oymn.spring5.Dept"></bean>
通过外部属性文件来操作bean:
例如配置数据库信息:
-
导入德鲁伊连接池jar包
-
创建外部属性文件,properties格式文件,写数据库信息
-
引入context名称空间,并通过context标签引入外部属性文件,使用“${}”来获取文件中对应的值
3. IOC操作Bean管理(基于注解)
-
格式:@注解名称(属性名=属性值,属性名=属性值,……)
-
注解可以作用在类,属性,方法。
-
使用注解的目的:简化xml配置
(1)基于注解创建对象:
spring提供了四种创建对象的注解:
- @Component
- @Service:一般用于Service层
- @Controller:一般用于web层
- @ Repository:一般用于Dao层
流程:
-
引入依赖:
-
开启组件扫描:扫描base-package包下所有有注解的类并为其创建对象
<context:component-scan base-package="com.oymn"></context:component-scan>
-
com.oymn.spring5.Service有一个stuService类
//这里通过@Component注解来创建对象,括号中value的值等同于之前xml创建对象使用的id,为了后面使用时通过id来获取对象 //括号中的内容也可以省略,默认是类名并且首字母小写 //可以用其他三个注解 @Component(value="stuService") public class StuService { public void add(){ System.out.println("addService"); } }
-
这样就可以通过getBean方法来获取stuService对象了
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean4.xml"); StuService stuService = context.getBean("stuService", StuService.class); System.out.println(stuService); stuService.add();
开启组件扫描的细节配置:
- use-default-fileters设置为false表示不使用默认过滤器,通过include-filter来设置只扫描com.oymn包下的所有@Controller修饰的类。
<context:component-scan base-package="com.oymn" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
- exclude-filter设置哪些注解不被扫描,例子中为@Controller修饰的类不被扫描
<context:component-scan base-package="com.oymn">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
(2)基于注解进行属性注入:
-
@Autowired:根据属性类型自动装配
创建StuDao接口和StuDaoImpl实现类,为StuDaoImpl添加创建对象注解
public interface StuDao { public void add(); }
@Repository public class StuDaoImpl implements StuDao { @Override public void add() { System.out.println("StuDaoImpl"); } }
StuService类中添加StuDao属性,为其添加@Autowire注解,spring会自动为stuDao属性创建StuDaoImpl对象
@Component(value="stuService") public class StuService { @Autowired public StuDao stuDao; public void add(){ System.out.println("addService"); stuDao.add(); } }
@Test public void test1(){ ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean4.xml"); StuService stuService = context.getBean("stuService", StuService.class); System.out.println(stuService); stuService.add(); }
测试结果:
-
@Qualifier:根据属性名称自动装配
当遇到一个接口有很多实现类时,只通过@Autowire是无法完成自动装配的,所以需要再使用@Qualifier通过名称来锁定某个类
@Component(value="stuService") public class StuService { @Autowired @Qualifier(value="stuDaoImpl") //这样就能显式指定stuDaoImpl这个实现类 public StuDao stuDao; public void add(){ System.out.println("addService"); stuDao.add(); } }
-
@Resource:可以根据类型注入,也可以根据名称注入
@Component(value="stuService") public class StuService { //@Resource //根据类型进行注入 @Resource(name="stuDaoImpl") //根据名称进行注入 public StuDao stuDao; public void add(){ System.out.println("addService"); stuDao.add(); } }
-
@Value:注入普通类型属性
@Value(value = "abc") private String name;
(3)完全注解开发:
创建配置类,替代xml配置文件
@Configuration //表明为一个配置类
@ComponentScan(basePackages = "com.oymn") //开启组件扫描
public class SpringConfig {
}
测试类:
@Test
public void test2(){
//创建AnnotationConfigApplicationContext对象
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
StuService stuService = context.getBean("stuService", StuService.class);
System.out.println(stuService);
stuService.add();
}
三、AOP
1. 底层原理
-
面向切面编程,利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。通俗来说就是在不修改代码的情况下添加新的功能。
-
底层通过动态代理来实现:
- 第一种:有接口的情况,使用JDK动态代理:创建接口实现类的代理对象。
- 第二种:无接口的情况,使用CGLIB动态代理:创建当前类子类的代理对象。
JDK动态代理举例:
-
通过 java.lang.reflect.Proxy类 的 newProxyInstance方法 创建代理类。
-
newProxyInstance方法:
-
参数一:类加载器
参数二:所增强方法所在的类,这个类实现的接口,支持多个接口
参数三:实现InvocationHandle接口,重写invoke方法来添加新的功能
代码举例:
public interface UserDao {
public int add(int a, int b);
public int multi(int a, int b);
}
public class UserDaoImpl implements UserDao {
@Override
public int add(int a, int b) {
return a+b;
}
@Override
public int multi(int a, int b) {
return a*b;
}
}
public class Main {
@Test
public void test1(){
//所需代理的类实现的接口,支持多个接口
Class[] interfaces = {UserDao.class};
UserDao userDao = new UserDaoImpl();
//调用newProxyInstance方法来创建代理类
UserDao userDaoProxy = (UserDao) Proxy.newProxyInstance(Main.class.getClassLoader(), interfaces, new UserDaoProxy(userDao));
int result = userDaoProxy.add(1, 2);
System.out.println(result);
}
//创建内部类,实现InvocationHandler接口,重写invoke方法,添加新功能
class UserDaoProxy implements InvocationHandler {
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 invoke = method.invoke(obj, args);
System.out.println("方法原先的内容执行完了");
return invoke;
}
}
}
运行结果:
2. 基于AspectJ实现AOP操作
(1)AOP相关术语:
- 连接点:类中可以被增强的方法,称为连接点。
- 切入点:实际被增强的方法,称为切入点。
- 通知:增强的那一部分逻辑代码。通知有多种类型:
- 前置通知:增强部分代码在原代码前面。
- 后置通知:增强部分代码在原代码后面。
- 环绕通知:增强部分代码既有在原代码前面,也有在原代码后面。
- 异常通知:原代码发生异常后才会执行。
- 最终通知:类似与finally那一部分
- 切面:指把通知应用到切入点这一个动作。
(2)基于AspectJ实现AOP有两种方式:
- 基于xml配置文件
- 基于注解方法
(3)切入点表达式
-
语法:execution([权限修饰符] [返回类型] [类全路径] [方法名称] [参数列表])
-
举例1:对 com.atguigu.dao.BookDao 类里面的 add 进行增强
execution(* com.auguigu.dao.BookDao.add(..))
-
举例2:对 com.atguigu.dao.BookDao 类里面的所有的方法进行增强
execution(* com.atguigu.dao.BookDao.*(..))
-
举例 3:对 com.atguigu.dao 包里面所有类,类里面所有方法进行增强
execution(* com.atguigu.dao.*.* (..))
(1)基于注解方式
@Component
public class User {
public void add(){
System.out.println("User.add()");
}
}
@Component
@Aspect //使用Aspect注解
public class UserProxy {
//前置通知
@Before(value="execution(* com.oymn.spring5.User.add(..))")
public void before(){
System.out.println("UserProxy.before()");
}
//后置通知
@AfterReturning(value="execution(* com.oymn.spring5.User.add(..))")
public void afterReturning(){
System.out.println("UserProxy.afterReturning()");
}
//最终通知
@After(value="execution(* com.oymn.spring5.User.add(..))")
public void After(){
System.out.println("UserProxy.After()");
}
//异常通知
@AfterThrowing(value="execution(* com.oymn.spring5.User.add(..))")
public void AfterThrowing(){
System.out.println("UserProxy.AfterThrowing()");
}
//环绕通知
@Around(value="execution(* com.oymn.spring5.User.add(..))")
public void Around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
System.out.println("UserProxy.Around() _1");
//调用proceed方法执行原先部分的代码
proceedingJoinPoint.proceed();
System.out.println("UserProxy.Around() _2");
}
}
配置xml文件:
<!--开启组件扫描-->
<context:component-scan base-package="com.oymn"></context:component-scan>
<!--开启AspectJ生成代理对象-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
测试类:
@Test
public void test2(){
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
User user = context.getBean("user", User.class);
user.add();
}
运行结果:
运行结果中没有出现异常通知,在add方法中添加int i = 1/0;
public void add(){
int i = 1/0;
System.out.println("User.add()");
}
运行结果:从这里也可以看到,但出现异常时,After最终通知有执行,而AfterReturning后置通知并没有执行。
对于上面的例子,有很多通知的切入点都是相同的方法,因此,可以将该切入点进行抽取:通过@Pointcut注解
@Pointcut(value="execution(* com.oymn.spring5.User.add(..))")
public void pointDemo(){
}
//前置通知
@Before(value="pointDemo()")
public void before(){
System.out.println("UserProxy.before()");
}
设置增强类优先级:
当有多个增强类对同一方法进行增强时,可以通过**@Order(数字值)来设置增强类的优先级,数字越小优先级越高。**
@Component
@Aspect
@Order(1)
public class PersonProxy
完全注解开发 :
可以通过配置类来彻底摆脱xml配置文件:
@Configuration
@ComponentScan(basePackages = "com.oymn.spring5")
//@EnableAspectJAutoProxy注解相当于上面xml文件中配置的 <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class Config {
}
(2)基于xml方式
这种方式开发中不怎么用,了解即可。
创建Book和BookProxy类
public class Book {
public void buy(){
System.out.println("buy()");
}
}
public class BookProxy {
public void before(){
System.out.println("before()");
}
}
配置xml文件:
<!--创建对象-->
<bean id="book" class="com.oymn.spring5.Book"></bean>
<bean id="bookProxy" class="com.oymn.spring5.BookProxy"></bean>
<aop:config>
<!--切入点-->
<aop:pointcut id="p" expression="execution(* com.oymn.spring5.Book.buy(..))"/>
<!--配置切面-->
<aop:aspect ref="bookProxy">
<aop:before method="before" pointcut-ref="p"/> <!--将bookProxy中的before方法配置为切入点的前置通知-->
</aop:aspect>
</aop:config>
四、JdbcTemplate
- Spring对JDBC进行封装,使用JdbcTemplate方便对数据库的操作。
(1)增删改操作:
int update(String sql, Object... args);
(2)查询:返回某个值
T queryForObject(String sql,Class<T> requiredType);
(3)查询:返回某个对象
T queryForObject(String sql,RowMapper<T> rowMapper,Object ... args);
(4)查询:返回集合
List<T> query(String sql,RowMapper<T> rowMapper,Object... args);
(5)批量增删改:
int[] batchUpdate(String sql,List<Object[]> batchArgs);
举例:
-
引入相关jar包
-
配置数据库连接池;配置JdbcTemplate对象
<context:component-scan base-package="com.oymn"></context:component-scan> <!--配置数据库连接池 --> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close"> <property name="url" value="jdbc:mysql://localhost:3306/book" /> <property name="username" value="root" /> <property name="password" value="000000" /> <property name="driverClassName" value="com.mysql.jdbc.Driver" /> </bean> <!--创建JdbcTemplate对象--> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <!--注入数据库连接池--> <property name="dataSource" ref="dataSource"></property> </bean>
-
创建Service类和Dao类,在Dao类中注入JdbcTemplate对象
public interface BookDao { public void add(Book book); //添加图书 public void update(Book book); //修改图书 public void delete(int id); //删除图书 public int queryCount(); //查询数量 public Book queryBookById(int id); //查询某本书 public List<Book> queryBooks(); //查询所有书 public void batchAddBook(List<Object[]> books); //批量添加图书 public void batchUpdateBook(List<Object[]> books); //批量修改图书 public void batchDeleteBook(List<Object[]> args); //批量删除图书 }
@Repository public class BookDaoImpl implements BookDao { @Autowired private JdbcTemplate jdbcTemplate; @Override public void add(Book book) { String sql = "insert into t_book set name=?,price=?"; Object[] args = {book.getBookName(),book.getBookPrice()}; int update = jdbcTemplate.update(sql, args); System.out.println(update); } @Override public void update(Book book) { String sql = "update t_book set name=?,price=? where id=?"; Object[] args = {book.getBookName(),book.getBookPrice(),book.getBookId()}; int update = jdbcTemplate.update(sql, args); System.out.println(update); } @Override public void delete(int id) { String sql = "delete from t_book where id=?"; int update = jdbcTemplate.update(sql, id); System.out.println(update); } @Override public int queryCount() { String sql = "select count(*) from t_book"; Integer count = jdbcTemplate.queryForObject(sql, Integer.class); return count; } @Override public Book queryBookById(int id) { String sql = "select id bookId,name bookName,price bookPrice from t_book where id=?"; Book book = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<Book>(Book.class), id); return book; } @Override public List<Book> queryBooks() { String sql = "select id bookId,name bookName,price bookPrice from t_book"; List<Book> bookList = jdbcTemplate.query(sql, new BeanPropertyRowMapper<Book>(Book.class)); return bookList; } @Override public void batchAddBook(List<Object[]> books) { String sql = "insert into t_book set id=?,name=?,price=?"; int[] ints = jdbcTemplate.batchUpdate(sql, books); System.out.println(ints); } @Override public void batchUpdateBook(List<Object[]> books) { String sql = "update t_book set name=?,price=? where id=?"; int[] ints = jdbcTemplate.batchUpdate(sql, books); System.out.println(ints); } @Override public void batchDeleteBook(List<Object[]> args) { String sql = "delete from t_book where id=?"; int[] ints = jdbcTemplate.batchUpdate(sql, args); System.out.println(ints); } }
@Service public class BookService { @Autowired private BookDao bookDao = new BookDaoImpl(); //添加图书 public void add(Book book){ bookDao.add(book); } //修改图书 public void update(Book book){ bookDao.update(book); } //删除图书 public void delete(Integer id){ bookDao.delete(id); } //查询数量 public int queryCount(){ return bookDao.queryCount(); } //查询图书 public Book queryBookById(Integer id){ return bookDao.queryBookById(id); } //查询所有图书 public List<Book> queryBooks(){ return bookDao.queryBooks(); } //批量添加图书 public void batchAddBook(List<Object[]> books){ bookDao.batchAddBook(books); } //批量修改图书 public void batchUpdateBook(List<Object[]> books){ bookDao.batchUpdateBook(books); } //批量删除图书 public void batchDeleteBook(List<Object[]> args){ bookDao.batchDeleteBook(args); } }
五、事务管理
-
事务是数据库操作最基本单位,要么都成功,要么都失败。
-
典型场景:转账
-
事务四个特性ACID:原子性,一致性,隔离性,持久性。
-
Spring事务管理有两种方式:编程式事务管理 和 声明式事务管理,一般使用声明式事务管理,底层使用AOP原理。
-
声明式事务管理有两种方式:基于xml配置方式 和 基于注解方式,一般使用注解方式。
-
Spring事务管理提供了一个接口,叫做事务管理器,这个接口针对不同的框架提供不同的实现类。
对于使用JdbcTemplate进行数据库交互,则使用DataSourceTransactionManager实现类,如果整合Hibernate框架则使用HibernateTransactionManager实现类,具体情况具体使用。
(1)注解实现声明式事务管理:
<!-- 数据库连接池 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
destroy-method="close">
<property name="url" value="jdbc:mysql://localhost:3306/book" />
<property name="username" value="root" />
<property name="password" value="000000" />
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
</bean>
<!--创建JdbcTemplate对象-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!--注入数据库连接池-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--创建事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--开启事务注解-->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
在service类上面或者service类的方法上面添加事务注解@Transactional
- 如果把@Transactional添加在类上面,这个类里面所有方法都添加事务。
- 如果只是添加在方法上面,则只为这个方法添加事务。
@Service
@Transactional
public class UserService {
声明式事务管理的参数配置:
-
propagation:事务传播行为,总共有7种,这一块讲的不是很清楚
-
isolation:事务隔离级别
有三个读问题:脏读,不可重复读,虚读(幻读)。
设置隔离级别,解决读问题:
脏读 不可重复读 虚读 READ UNCOMMITED(读未提交) 有 有 有 READ COMMITED(读已提交) 无 有 有 REPEATABLE READ(可重复读) 无 无 有 SERIALIZABLE(串行化) 无 无 无 -
timeout:超时时间
- 事务需要在一定时间内进行提交,超过时间后回滚。
- 默认值是-1,设置时间以秒为单位。
- readOnly:是否只读
- 默认值为false,表示可以查询,也可以增删改。
- 设置为true,只能查询。
- rollbackFor:回滚,设置出现哪些异常进行事务回滚。
- noRollbackFor:不回滚,设置出现哪些异常不进行事务回滚。
@Service
@Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.READ_COMMITTED)
public class AccountService {
完全注解实现声明式事务管理:
配置类:
@Configuration //配置类
@ComponentScan(basePackages = "com.oymn.spring5") //开启组件扫描
@EnableTransactionManagement //开启事务
public class Config {
//创建数据库连接池
@Bean
public DruidDataSource getDruidDataSource(){
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setDriverClassName("com.mysql.jdbc.Driver");
druidDataSource.setUrl("jdbc:mysql://localhost:3306/book");
druidDataSource.setUsername("root");
druidDataSource.setPassword("000000");
return druidDataSource;
}
//创建JdbcTemplate对象
@Bean
public JdbcTemplate getJdbcTemplate(DataSource dataSource){
JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(dataSource);
return jdbcTemplate;
}
//创建事务管理器
@Bean
public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource){
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource);
return transactionManager;
}
}
@Service
public class AccountService {
@Autowired
private AccountDao accountDao;
@Transactional
public void accountMoney(){
accountDao.add();
//int i=1/0; //用来模拟转账失败
accountDao.reduce();
}
}
(2)xml实现声明式事务管理:
<context:component-scan base-package="com.oymn"></context:component-scan>
<!-- 数据库连接池 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
destroy-method="close">
<property name="url" value="jdbc:mysql://localhost:3306/book" />
<property name="username" value="root" />
<property name="password" value="000000" />
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
</bean>
<!--创建JdbcTemplate对象-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!--注入数据库连接池-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--创建事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--配置事务通知-->
<tx:advice id="txadvice">
<!--配置事务参数-->
<tx:attributes>
<tx:method name="accountMoney" propagation="REQUIRED" />
</tx:attributes>
</tx:advice>
<!--配置切入点和切面-->
<aop:config>
<!--配置切入点-->
<aop:pointcut id="pt" expression="execution(* com.oymn.spring5.Service.*.*(..))"/>
<!--配置切面-->
<aop:advisor advice-ref="txadvice" pointcut-ref="pt"/>
</aop:config>
六、Spring5新特性
1. 自带了日志封装
- Spring5移除了Log4jConfigListener,官方建议使用Log4j2
Spring5整合Log4j2:
第一步:引入jar包
第二步:创建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>
2. @Nullable注解
-
@Nullable注解可以用在方法上,属性上,参数上,表示方法返回值可以为空,属性可以为空,参数可以为空。
@Nullable //表示方法返回值可以为空 public int getId(); @Nullable //表示参数可以为空 public void setId(@Nullable int Id); @Nullable //表示属性可以为空 public int id;
3. 支持函数式风格编程
这是因为java8新增了lamda表达式
@Test
public void test() {
//1 创建 GenericApplicationContext 对象
GenericApplicationContext context = new GenericApplicationContext();
//2 调用 context 的方法对象注册
context.refresh();
context.registerBean("user1",User.class,() -> new User());
//3 获取在 spring 注册的对象
// User user = (User)context.getBean("com.atguigu.spring5.test.User");
User user = (User)context.getBean("user1");
System.out.println(user);
}
4. 支持整合JUnit5
(1)整合JUnit4:
第一步:引入jar包
第二步:创建测试类,使用注解方式完成
@RunWith(SpringJUnit4ClassRunner.class) //单元测试框架
@ContextConfiguration("classpath:bean4.xml") //加载配置文件
public class JUnitTest {
@Autowired
public User user;
@Test
public void test(){
System.out.println(user);
}
}
bean4.xml:
<context:component-scan base-package="com.oymn"></context:component-scan>
通过使用@ContextConfiguration注解,测试方法中就不用每次都通过context来获取对象了,比较方便。
ApplicationContext context = new ClassPathXmlApplicationContext("bean2.xml");
BookService bookService = context.getBean("bookService",BookService.class);
(2)整合JUnit5: