简介
Spring Framework
Spring Framework为基于Java的现代企业级应用程序提供了全面的编程和配置模型。主要作用是为连接应用程序的不同组件提供技术衔接。
什么是Spring
Spring是一个轻量级Java开发框架, Spring最根本的使命是解决企业级应用开发的复杂性,即简化Java开发。Spring功能的底层都依赖于它的两个核心特性,也就是依赖注入(dependency injection,DI)和面向切面编程(aspect-oriented programming,AOP)。降低Java开发复杂性的四个策略:
- 基于POJO的轻量级和最小入侵性编程。
- 通过依赖注入和面向接口实现松耦合。
- 基于切面和惯例进行声明式编程。
- 通过切面和模板减少样板式代码。
Spring中用了哪些设计模式:工厂模式、单例模式、代理模式、模板方法、观察者模式。
IOC
什么是 IOC容器
控制反转(Inversion Of Control), 它把传统上由程序代码直接操控的对象的调用权交给容器,通过容器来实现对象组件的装配和管理。 Spring IOC 负责创建对象,管理对象(通过依赖注入(DI),装配对象,配置对象,并且管理这些对象的整个生命周期。
IOC的作用
- 管理对象的创建和依赖关系的维护
- 解耦
- 托管了类的产生过程
IOC支持的功能
- 依赖注入
- 依赖检查
- 自动装配
- 支持集合
- 指定初始化方法和销毁方法
- 支持回调某些方法
IOC 底层原理
XML解析 + 工厂模式 + 反射
工厂模式
在接口定义了创建对象的方法,将具体的创建对象的过程在子类中实现,用户只需要通过接口创建需要的对象。(用工厂方法代替new操作创建一个实例对象的方法)
XML解析
获取类的属性,路径等信息。
反射
在实现工厂模式的类中,利用从 xml 解析出来的路径和Class.forName() 方法拿到类对象。类对象通过 newInstance() 方法创建对象。
IOC 的接口
BeanFactory
IOC容器基本实现,是 Spring 内部的使用接口,不提供开发人员进行使用。
懒加载:加载配置文件的时候不会创建对象,在获取对象的时候才去创建对象。
ApplicationContext
BeanFactory的子接口,一般由开发人员使用。
饿加载:加载配置文件的时候就会创建对象。
IOC bean管理
bean管理指两个操作:创建对象、注入属性
实现的方式:
- 基于xml配置文件方式实现
- 基于注解方式实现
XML
基于XML配置文件创建对象
<!--1 配置User对象创建-->
<bean id="user" class="com.atguigu.spring5.User"></bean>
基于XML方式注入属性
通过DI(依赖注入)实现注入属性。
-
使用 set() 方法实现注入。
<bean id="book" class="com.atguigu.spring5.Book"> <!--使用property完成属性注入 name:类里面属性名称 value:向属性注入的值 --> <property name="bname" value="Hello"></property> <property name="bauthor" value="World"></property> </bean>
-
使用有参构造方法实现注入
<!--spring方式:有参数构造注入属性--> <bean id="orders" class="com.atguigu.spring5.Orders"> <constructor-arg name="oname" value="Hello"></constructor-arg> <constructor-arg name="address" value="China!"></constructor-arg> </bean>
-
p名称空间注入(了解即可)
<!--1、添加p名称空间在配置文件头部--> <!--在这里添加一行p名称的约束--> xmlns:p="http://www.springframework.org/schema/p" <!--2、在bean标签进行属性注入(算是set方式注入的简化操作)--> <bean id="book" class="com.atguigu.spring5.Book" p:bname="very" p:bauthor="good"> </bean>
-
外部bean注入
public class UserService {//service类 //创建UserDao类型属性,生成set方法 private UserDao userDao; public void setUserDao(UserDao userDao) { this.userDao = userDao; } }
<!--1 service和dao对象创建--> <bean id="userService" class="com.atguigu.spring5.service.UserService"> <!--注入userDao对象 name属性:类里面属性名称 ref属性:创建userDao对象bean标签id值 --> <property name="userDao" ref="userDaoImpl"></property> </bean> <bean id="userDaoImpl" class="com.atguigu.spring5.dao.UserDaoImpl"></bean>
-
级联赋值
<!--方式一:级联赋值--> <bean id="emp" class="com.atguigu.spring5.bean.Emp"> <!--设置两个普通属性--> <property name="ename" value="Andy"></property> <property name="gender" value="女"></property> <!--级联赋值--> <property name="dept" ref="dept"></property> </bean> <bean id="dept" class="com.atguigu.spring5.bean.Dept"> <property name="dname" value="公关部门"></property> </bean> <!--方式二:级联赋值--> <bean id="emp" class="com.atguigu.spring5.bean.Emp"> <!--设置两个普通属性--> <property name="ename" value="jams"></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"> </bean>
-
各种集合类的注入
<bean id="stu" class="com.atguigu.spring5.collectiontype.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="maps"> <map> <entry key="JAVA" value="java"></entry> <entry key="PHP" value="php"></entry> </map> </property> <!--set类型属性注入--> <property name="sets"> <set> <value>MySQL</value> <value>Redis</value> </set> </property> </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>
<!--第一步:在 spring 配置文件中引入名称空间 util--> <?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:util="http://www.springframework.org/schema/util" <!--添加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"> <!--添加util名称空间--> <!--第二步:使用 util 标签完成 list 集合注入提取--> <!--把集合注入部分提取出来--> <!--1 提取list集合类型属性注入--> <util:list id="bookList"> <value>易筋经</value> <value>九阴真经</value> <value>九阳神功</value> </util:list> <!--2 提取list集合类型属性注入使用--> <bean id="book" class="com.atguigu.spring5.collectiontype.Book" scope="prototype"> <property name="list" ref="bookList"></property> </bean>
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>
引入外部属性文件
方式一:直接配置数据库信息 :(1)配置Druid(德鲁伊)连接池 (2)引入Druid(德鲁伊)连接池依赖 jar 包
方式二:引入外部属性文件配置数据库连接池
-
创建外部属性文件,properties 格式文件,写数据库信息(jdbc.properties)。
prop.driverClass=com.mysql.jdbc.Driver prop.url=jdbc:mysql://localhost:3306/userDb prop.userName=root prop.password=root
-
把外部 properties 属性文件引入到 spring 配置文件中 —— 引入 context 名称空间。
<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/context/spring-context.xsd"> <!--上面,引入context名称空间--> <!--引入外部属性文件--> <context:property-placeholder location="classpath:jdbc.properties"/> <!--配置连接池--> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="${prop.driverClass}"></property> <property name="url" value="${prop.url}"></property> <property name="username" value="${prop.userName}"></property> <property name="password" value="${prop.password}"></property> </bean> </beans>
Bean
工厂bean
Spring 有两种类型 bean,一种普通 bean,另外一种工厂 bean(FactoryBean)
- 普通 bean:在配置文件中定义 bean 类型就是返回类型。
- 工厂 bean:在配置文件定义 bean 类型可以和返回类型不一样。
- 第一步 创建类,让这个类作为工厂 bean,实现接口 FactoryBean。
- 第二步 实现接口里面的方法,在实现的方法中定义返回的 bean 类型
bean 作用域
在 Spring 里面,默认情况下,bean 是单实例对象。
进行作用域设置:
在 spring 配置文件 bean 标签里面有属性(scope)用于设置单实例还是多实例。
-
scope 属性值:
- 第一个值 默认值,singleton,表示是单实例对象。
- 第二个值 prototype,表示是多实例对象。
-
singleton 和 prototype 区别:
- singleton 单实例,prototype 多实例。
- 设置 scope 值是 singleton 时候,加载 spring 配置文件时候就会创建单实例对象 ;设置 scope 值是 prototype 时候,不是在加载 spring 配置文件时候创建对象,在调用 getBean 方法时候创建多实例对象。
bean 生命周期
生命周期 :从对象创建到对象销毁的过程。
- 通过构造器创建 bean 实例(无参数构造)。
- 为 bean 的属性设置值和对其他 bean 引用(调用 set 方法)。
- 把 bean 实例传递 bean 后置处理器的方法 postProcessBeforeInitialization。
- 调用 bean 的初始化的方法(需要进行配置初始化的方法)。
- 把 bean 实例传递 bean 后置处理器的方法 postProcessAfterInitialization。
- bean 可以使用了(对象获取到了)。
- 当容器关闭时候,调用 bean 的销毁的方法(需要进行配置销毁的方法)。
//创建后置处理器实现类
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;
}
}
<!--配置文件的bean参数配置-->
<bean id="orders" class="com.atguigu.spring5.bean.Orders" init-method="initMethod" destroy-method="destroyMethod"> <!--配置初始化方法和销毁方法-->
<property name="oname" value="手机"></property><!--这里就是通过set方式(注入属性)赋值-->
</bean>
<!--配置后置处理器-->
<bean id="myBeanPost" class="com.atguigu.spring5.bean.MyBeanPost"></bean>
注解
注解是代码特殊标记,格式:@注解名称(属性名称=属性值, 属性名称=属性值…)。
使用注解目的:简化xml配置。
Bean中创建对象提供的注解
下面四个注解功能是一样的,都可以用来创建 bean 实例:
@Component
@Service
@Controller
@Repository
基于注解方式实现对象创建
第一步 引入依赖 (引入spring-aop jar包)
-
第二步 开启组件扫描
<!--开启组件扫描 1 如果扫描多个包,多个包使用逗号隔开 2 扫描包上层目录 --> <context:component-scan base-package="com.atguigu"></context:component-scan>
-
第三步 创建类,在类上面添加创建对象注解
/*在注解里面 value 属性值可以省略不写, 默认值是类名称,首字母小写 UserService -- userService */ //注解等同于XML配置文件:<bean id="userService" class=".."/> @Component(value = "userService") public class UserService { public void add() { System.out.println("service add......."); } }
开启组件扫描细节配置
<!--示例 1
use-default-filters="false" 表示现在不使用默认 filter,自己配置 filter
context:include-filter ,设置扫描哪些内容
-->
<context:component-scan base-package="com.atguigu" use-defaultfilters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
<!--代表只扫描Controller注解的类-->
</context:component-scan>
<!--示例 2
下面配置扫描包所有内容
context:exclude-filter: 设置哪些内容不进行扫描
-->
<context:component-scan base-package="com.atguigu">
<context:exclude-filter type="annotation"
expression="org.springframework.stereotype.Controller"/>
<!--表示Controller注解的类之外一切都进行扫描-->
</context:component-scan>
基于注解方式实现属性注入
@Autowired:根据属性类型进行自动装配。
第一步 把 service 和 dao 对象创建,在 service 和 dao 类添加创建对象注解。
第二步 在 service 注入 dao 对象,在 service 类添加 dao 类型属性,在属性上面使用注解。
@Service
public class UserService {
//定义 dao 类型属性
//不需要添加 set 方法
//添加注入属性注解
@Autowired
private UserDao userDao;
public void add() {
System.out.println("service add.......");
userDao.add();
}
}
//Dao实现类
@Repository
//@Repository(value = "userDaoImpl1")
public class UserDaoImpl implements UserDao {
@Override
public void add() {
System.out.println("dao add.....");
}
}
@Qualifier:根据名称进行注入。
这个@Qualifier 注解的使用,和上面@Autowired 一起使用。
//定义 dao 类型属性
//不需要添加 set 方法
//添加注入属性注解
@Autowired //根据类型进行注入
//根据名称进行注入(目的在于区别同一接口下有多个实现类,根据类型就无法选择,从而出错!)
@Qualifier(value = "userDaoImpl1")
private UserDao userDao;
@Resource:可以根据类型注入,也可以根据名称注入
它属于javax包下的注解,不推荐使用!
//@Resource //根据类型进行注入
@Resource(name = "userDaoImpl1") //根据名称进行注入
private UserDao userDao;
@Value:注入普通类型属性
@Value(value = "abc")
private String name
完全注解开发
-
创建配置类,替代 xml 配置文件。
@Configuration //作为配置类,替代 xml 配置文件 @ComponentScan(basePackages = {"com.atguigu"}) public class SpringConfig { }
-
编写测试类。
@Test public void testService2() { //加载配置类 ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class); UserService userService = context.getBean("userService",UserService.class); System.out.println(userService); userService.add(); }
AOP
AOP 基本概念
面向切面编程(方面),利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得 业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
- 通俗描述:不通过修改源代码方式,在主干功能里面添加新功能。
AOP 底层原理
Spring会根据 需要代理的类是否有实现接口 而动态选择JDK代理或CGlib代理。
代理模式介绍
简介
代理模式为一个对象提供一个替身,以控制对这个对象的访问,即通过代理对象访问目标对象。
这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能。
代理模式分为:静态代理、动态代理(又分为:JDK代理、Cglib代理)。
静态代理
在使用时,需要定义接口或者父类,被代理对象(即目标对象)与代理对象一起实现相同的接口或者是继承相同的父类。
动态代理
- 代理对象 不需要实现接口,但是目标对象要实现接口,否则不能用动态代理。
- 代理对象的生成 是利用 JDK 的 API,动态的在内存中构建代理对象(反射)。
- 动态代理也叫做:JDK代理、接口代理。
代理类所在包:java.lang.reflect.Proxy
Cglib代理
- 静态代理和JDK代理都要求目标对象的类实现一个接口,但是有时候目标对象没有实现任何接口,这个时候可以使用目标对象的类的子类来实现代理=>Cglib代理。
- Cglib代理也叫子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能的扩展。
- Cglib是一个强大的高性能的代码生成包,它可以在运行期间扩展 java类与实现 java类接口。它广泛的被许多AOP的框架使用。
- Cglib包的底层是通过使用字节码处理框架ASM来转换字节码并生成新的类。
JDK 动态代理
使用 JDK 动态代理,使用 Proxy 类里面的方法创建代理对象。
调用 newProxyInstance 方法,方法有三个参数:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
第一参数,类加载器。
第二参数,增强方法所在的类,这个类实现的接口,支持多个接口。
第三参数,实现这个接口 InvocationHandler,创建代理对象,写增强的部分。
public class ZProxy{//代理类
//接口的引用
private Z taget;
public ZProxy(Z taget){
this.taget = taget;
}
//获取代理对象
public Object getProxy(){
return Proxy.newProxyInstance(taget.getClass().getClassLoader(),
taget.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("我来啦!小赵~代理开始~");
Object rval = method.invoke(taget, args);
return rval;
}
});
}
}
public static void main(String[] args) {
//创建目标对象
Z xz = new ZZZ();
//给目标对象 创建代理对象
Z pInstance = (Z)new ZProxy(xz).getProxy();
//通过代理对象调用目标对象的方法
pInstance.giao();
}
AOP 操作
AOP 术语
- 连接点:类里面哪些方法可以被增强,这些方法称为连接点。
- 切入点:实际被真正增强的方法称为切入点。
- 通知(增强):
- 实际增强的逻辑部分称为通知。
- 分为以下五种类型:前置通知、后置通知、环绕通知、异常通知、最终通知。
- 切面:把通知应用到切入点过程。
AspectJ 简述
Spring 框架一般都是基于 AspectJ 实现 AOP 操作。AspectJ 是独立 AOP 框架,它不是 Spring 组成部分。一般把 AspectJ 和 Spirng 框架一起使用,进行 AOP 操作
基于 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.. (..))
AspectJ 注解
//1、创建类,在类里面定义方法
@Component
public class User {
public void add() {
System.out.println("add.......");
}
}
//2、创建增强类(编写增强逻辑)
//(1)在增强类里面,创建方法,让不同方法代表不同通知类型
//增强的类
@Component
@Aspect //生成代理对象
public class UserProxy {
//相同切入点抽取
@Pointcut(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))")
public void pointdemo() {
}
//前置通知
//@Before注解表示作为前置通知
@Before(value = "pointdemo()")//相同切入点抽取使用!
public void before() {//前置通知
System.out.println("before......");
}
//后置通知(返回通知)
@AfterReturning(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))")
public void afterReturning() {
System.out.println("afterReturning.........");
}
}
<!--3、进行通知的配置-->
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 开启注解扫描 -->
<context:component-scan base-package="com.atguigu.spring5.aopanno"></context:component-scan>
<!-- 开启Aspect生成代理对象-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
有多个增强类对同一个方法进行增强,设置增强类优先级
//(1)在增强类上面添加注解 @Order(数字类型值),数字类型值越小优先级越高
@Component
@Aspect
@Order(1)
public class PersonProxy{ }
AspectJ 配置文件
<!--1、创建两个类,增强类和被增强类,创建方法(同上一样)-->
<!--2、在 spring 配置文件中创建两个类对象-->
<!--创建对象-->
<bean id="book" class="com.atguigu.spring5.aopxml.Book"></bean>
<bean id="bookProxy" class="com.atguigu.spring5.aopxml.BookProxy"></bean>
<!--3、在 spring 配置文件中配置切入点-->
<!--配置 aop 增强-->
<aop:config>
<!--切入点-->
<aop:pointcut id="p"
expression="execution(*com.atguigu.spring5.aopxml.Book.buy(..))"/>
<!--配置切面-->
<aop:aspect ref="bookProxy">
<!--增强作用在具体的方法上-->
<aop:before method="before" pointcut-ref="p"/>
</aop:aspect>
</aop:config>
JDBCTemplate
JdbcTemplate概念及使用
Spring 框架对 JDBC 进行封装,使用 JdbcTemplate 方便实现对数据库操作。
-
在 spring 配置文件配置数据库连接池。
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close"> <property name="url" value="jdbc:mysql:///test" /> <property name="username" value="root" /> <property name="password" value="root" /> <property name="driverClassName" value="com.mysql.jdbc.Driver" /> </bean>
-
配置 JdbcTemplate 对象,注入 DataSource。
<!-- JdbcTemplate 对象 --> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <!--注入 dataSource--> <property name="dataSource" ref="dataSource"></property><!--set方式注入--></bean>
-
创建 service 类,创建 dao 类,在 dao 注入 jdbcTemplate 对象。
<!-- 组件扫描 --> <context:component-scan base-package="com.atguigu"></context:component-scan>
@Service public class BookService { //注入 dao @Autowired private BookDao bookDao; } @Repository public class BookDaoImpl implements BookDao { //注入 JdbcTemplate @Autowired private JdbcTemplate jdbcTemplate; }
JdbcTemplate 操作数据库
添加
对应数据库创建实体类,创建service和dao。
调用 JdbcTemplate 对象里面 “update” 方法实现添加操作。
@Repository
public class BookDaoImpl implements BookDao {
//注入 JdbcTemplate
@Autowired
private JdbcTemplate jdbcTemplate;
//添加的方法
@Override
public void add(Book book) {
//1 创建 sql 语句
String sql = "insert into t_book values(?,?,?)";
//2 调用方法实现
Object[] args = {book.getUserId(), book.getUsername(),book.getUstatus()};
int update = jdbcTemplate.update(sql,args);
System.out.println(update);
}
}
修改和删除
使用JdbcTemplate 模板所实现的 “增删改” 都是调用了同一个 “update” 方法
//1、修改
@Override
public void updateBook(Book book) {
String sql = "update t_book set username=?,ustatus=? where user_id=?";
Object[] args = {book.getUsername(), book.getUstatus(),book.getUserId()};
int update = jdbcTemplate.update(sql, args);
System.out.println(update);
}
//2、删除
@Override
public void delete(String id) {
String sql = "delete from t_book where user_id=?";
int update = jdbcTemplate.update(sql, id);
System.out.println(update);
}
查询返回某个值
//查询表记录数
@Override
public int selectCount() {
String sql = "select count(*) from t_book";
//queryForObject方法中:第一个参数代表--sql语句;第二个参数代表--返回类型class
Integer count = jdbcTemplate.queryForObject(sql, Integer.class);
return count;
}
查询返回对象
//查询返回对象
@Override
public Book findBookInfo(String id) {
String sql = "select * from t_book where user_id=?";
//调用方法
/*
queryForObject方法中:
第一个参数:sql语句
第二个参数:RowMapper 是接口,针对返回不同类型数据,使用这个接口里面 实现类 完成数据封装
第三个参数:sql 语句值
*/
Book book = jdbcTemplate.queryForObject(sql,
new BeanPropertyRowMapper<Book(Book.class), id);
return book;
}
查询返回集合
使用的场景:查询图书列表分页。
//查询返回集合
@Override
public List<Book> findAllBook() {
String sql = "select * from t_book";
//调用方法
List<Book> bookList = jdbcTemplate.query(sql,
new BeanPropertyRowMapper<Book>(Book.class));
return bookList;
}
批量添加
//批量添加
@Override
public void batchAddBook(List<Object[]> batchArgs) {
String sql = "insert into t_book values(?,?,?)";
//batchUpdate方法 第一个参数:sql语句 第二个参数:List集合,添加多条记录数据
int[] ints = jdbcTemplate.batchUpdate(sql, batchArgs);
System.out.println(Arrays.toString(ints));
}
//批量添加测试
List<Object[]> batchArgs = new ArrayList<>();
Object[] o1 = {"3","java","a"};
Object[] o2 = {"4","c++","b"};
Object[] o3 = {"5","MySQL","c"};
batchArgs.add(o1);
batchArgs.add(o2);
batchArgs.add(o3);
//调用批量添加
bookService.batchAdd(batchArgs);
批量修改
批量修改同批量添加一样,调用同一个方法
@Override
public void batchUpdateBook(List<Object[]> batchArgs) {
String sql = "update t_book set username=?,ustatus=? where user_id=?";
int[] ints = jdbcTemplate.batchUpdate(sql, batchArgs);
System.out.println(Arrays.toString(ints));
}
事务
事务基本概念
事务是数据库操作最基本单元,逻辑上一组操作,要么都成功,如果有一个失败所有操作都失败。
事务的四大特性
- 原子性(Atomicity):事务是一个原子操作,由一系列动作组成。事务的原子性确保动作要么全部完成,要么完全不起作用。
- 一致性(Consistency):一旦事务完成(不管成功还是失败),系统必须确保它所建模的业务处于一致的状态,而不会是部分完成部分失败。在现实中的数据不应该被破坏。
- 隔离性(Isolation):可能有许多事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。
- 持久性(Durability):一旦事务完成,无论发生什么系统错误,它的结果都不应该受到影响,这样就能从任何系统崩溃中恢复过来。通常情况下,事务的结果被写到持久化存储器中。
Spring事务管理
Spring事务管理有两种方式。
编程式事务管理
编程式事务管理是侵入性事务管理,使用TransactionTemplate或者直接使用PlatformTransactionManager,对于编程式事务管理,Spring推荐使用TransactionTemplate。
声明式事务管理
声明式事务管理建立在AOP之上,其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,执行完目标方法之后根据执行的情况提交或者回滚。编程式事务每次实现都要单独实现,但业务量大功能复杂时,使用编程式事务无疑是痛苦的,而声明式事务不同,声明式事务属于无侵入式,不会影响业务逻辑的实现,只需要在配置文件中做相关的事务规则声明或者通过注解的方式,便可以将事务规则应用到业务逻辑中。
显然声明式事务管理要优于编程式事务管理,这正是Spring倡导的非侵入式的编程方式。唯一不足的地方就是声明式事务管理的粒度是方法级别,而编程式事务管理是可以到代码块的,但是可以通过提取方法的方式完成声明式事务管理的配置。
声明式事务管理实现
- 基于注解方式(使用)
- 基于xml配置方式
Spring提供了一个PlatformTransactionManager
接口,代表事务管理器,这个接口针对不同的框架提供不同的实现类。
注解式事务管理操作
-
在Spring配置文件中,添加事务管理器,并开启事务注解这里需要注意,开启事务注解需要使用名称空间tx。
xmlns:tx="http://www.springframework.org/schema/tx" <!--创建事务管理器--> <bean id="transactionManager" class= "org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!--注入数据源--> <property name="dataSource" ref="dataSource"/> </bean> <!--开启事务注解--> <tx:annotation-driven transaction-manager="transactionManager"/>
-
在service类或者类方法上中添加transactional注解。
@Transactional,这个注解添加到类上面,也可以添加方法上面。
- 如果把这个注解添加类上面,这个类里面所有的方法都添加事务
- 如果把这个注解添加方法上面,为这个方法添加事务
@Service @Transactional // 事务注解,类上面或者里面的方法上添加注解 public class UserService { @Autowired private UserDao userDao; }
事务配置的相关参数
在 service 类上面添加注解@Transactional,在这个注解里面可以配置事务相关参数。
一、事务的传播行为
事务的传播性一般用在事务嵌套的场景,比如一个事务方法里面调用了另外一个事务方法,那么两个方法是各自作为独立的方法提交还是内层的事务合并到外层的事务一起提交,这就是需要事务传播机制的配置来确定怎么样执行。
事务的传播行为有以下七种:
-
PROPAGATION_REQUIRED
:Spring默认的传播机制,能满足绝大部分业务需求,如果外层有事务,则当前事务加入到外层事务,一块提交,一块回滚。如果外层没有事务,新建一个事务执行。 -
PROPAGATION_REQUES_NEW
:该事务传播机制是每次都会新开启一个事务,同时把外层事务挂起,当当前事务执行完毕,恢复上层事务的执行。如果外层没有事务,执行当前新开启的事务即可。 -
PROPAGATION_SUPPORT
:如果外层有事务,则加入外层事务,如果外层没有事务,则直接使用非事务方式执行。完全依赖外层的事务。 -
PROPAGATION_NOT_SUPPORT
:该传播机制不支持事务,如果外层存在事务则挂起,执行完当前代码,则恢复外层事务,无论是否异常都不会回滚当前的代码。 -
PROPAGATION_NEVER
:该传播机制不支持外层事务,即如果外层有事务就抛出异常。 -
PROPAGATION_MANDATORY
:与NEVER相反,如果外层没有事务,则抛出异常。 -
PROPAGATION_NESTED
:该传播机制的特点是可以保存状态保存点,当前事务回滚到某一个点,从而避免所有的嵌套事务都回滚,即各自回滚各自的,如果子事务没有把异常吃掉,基本还是会引起全部回滚的。
传播规则回答了这样一个问题:一个新的事务应该被启动还是被挂起,或者是一个方法是否应该在事务性上下文中运行。
二、事务的隔离级别
事务的隔离级别定义一个事务可能受其他并发务活动活动影响的程度,可以把事务的隔离级别想象为这个事务对于事物处理数据的自私程度。
三个读问题
在一个典型的应用程序中,多个事务同时运行,经常会为了完成他们的工作而操作同一个数据。并发虽然是必需的,但是会导致以下问题:
- 脏读:脏读发生在一个事务读取了被另一个事务改写但尚未提交的数据时。如果这些改变在稍后被回滚了,那么第一个事务读取的数据就会是无效的。
- 不可重复读:不可重复读发生在一个事务执行相同的两次或两次以上查询,但每次查询结果都不相同时。这通常是由于另一个并发事务在两次查询之间更新了数据。不可重复读重点在修改。
- 幻读:幻读和不可重复读相似。当一个事务(T1)读取几行记录后,另一个并发事务(T2)插入了一些记录时,幻读就发生了。在后来的查询中,第一个事务(T1)就会发现一些原来没有的额外记录。幻读重点在新增或删除。
隔离级别
在理想状态下,事务之间将完全隔离,从而可以防止这些问题发生。然而,完全隔离会影响性能,因为隔离经常涉及到锁定在数据库中的记录(甚至有时是锁表)。完全隔离要求事务相互等待来完成工作,会阻碍并发。因此,可以根据业务场景选择不同的隔离级别。
三、timeout:超时时间
- 事务需要在一定时间内进行提交,如果不提交进行回滚。
- 默认值是 -1,设置时间以秒单位进行计算。
四、readOnly:是否只读
- 读:查询操作,写:添加修改删除操作 。
- readOnly默认值 false,表示可以查询,可以添加修改删除操作。
- 设置 readOnly值是 true,设置成 true之后,只能查询。
五、rollbackFor:回滚
设置出现哪些异常进行事务回滚。
六、noRollbackFor:不回滚
设置出现哪些异常不进行事务回滚。
XML声明式事务管理
- 配置事务管理器。
- 配置通知。
- 配置切入点和切面。
<!--1 创建事务管理器-->
<bean id="transactionManager" class=
"org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入数据源-->
<property name="dataSource" ref="dataSource"/>
</bean>
<!--2 配置通知-->
<tx:advice id = "tx_advice">
<!--配置事务的一些相关参数-->
<tx:attributes>
<!--指定哪种规则的方法上添加事务-->
<tx:method name="account*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<!--3 配置切入点和切面-->
<aop:config>
<!--配置切入点-->
<aop:pointcut id="pt" expression=
"execution(* com.micah.spring.service.UserService.*(..))"/>
<!--配置切面-->
<aop:advisor advice-ref="tx_advice" pointcut-ref="pt"/>
</aop:config>
完全注解声明式事务管理
创建配置类,用来替代配置文件。
@Configuration
@ComponentScan(basePackages = "com.micah") // 组建扫描
@EnableTransactionManagement //开启事务
public class TxConfig {
/**
* 创建数据库连接池
*/
@Bean
public DruidDataSource getDruidDataSource(){
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setDriverClassName("com.mysql.jdbc.Driver");
druidDataSource.setUrl("jdbc:mysql:///user_db");
druidDataSource.setUsername("root");
druidDataSource.setPassword("jlq000321");
return druidDataSource;
}
/**
* 创建jdbcTemplate对象
*/
@Bean
public JdbcTemplate getJdbcTemplate(DataSource dataSource){
// 到ioc容器中,根据类型找到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;
}
}
Spring 5 新特性
Spring Framework 5.0是在Spring Framework 4.0之后将近四年内一次重大的升级。 在这个时间框架内,主要的发展之一就是Spring Boot项目的演变。
概述
Spring Framework 5.0的最大特点之一是响应式编程(Reactive Programming)。 响应式编程核心功能和对响应式endpoints的支持可通过Spring Framework 5.0中获得。 重要变动如下列表所示:
- 对JDK 9运行时兼容性。
- 在Spring Framework代码中使用JDK 8特性。
- 响应式编程支持。
- 函数式Web框架。
- Jigsaw的Java模块化。
- 对Kotlin支持。
- 舍弃的特性。
基于Java8兼容Java9
整个 Spring5 框架的代码基于 Java8,运行时兼容 JDK9,许多不建议使用的类和方法在代码库中删除。
使用的一些Java 8特性如下:
- 核心Spring接口中的Java 8 static 方法。
- 基于Java 8反射增强的内部代码改进。
- 在框架代码中使用函数式编程——lambdas表达式和stream流。
支持响应式编程
响应式编程是Spring Framework 5.0最重要的功能之一。
微服务通常基于事件通信的架构构建。 应用程序被设计为对事件(或消息)做出反应。
响应式编程提供了一种可选的编程风格,专注于构建应对事件的应用程序。
虽然Java 8没有内置的响应式性编程支持,但是有一些框架提供了对响应式编程的支持:
-
Reactive Streams
:尝试定义与语言无关的响应性API。 -
Reactor
:Spring Pivotal团队提供的响应式编程的Java实现。 -
Spring WebFlux
:启用基于响应式编程的Web应用程序的开发。 提供类似于Spring MVC的编程模型。
函数式Web框架
除了响应式特性之外,Spring 5还提供了一个函数式Web框架。
函数式Web框架提供了使用函数式编程风格来定义endpoints的功能。
Spring 5之日志框架
Spring 5.0 框架自带了通用的日志封装
Spring5已经移除 Log4jConfigListener,官方建议使用 Log4j2。
Spring5框架整合 Log4j2。
通过xml方式配置 Log4j2
<?xml version="1.0" encoding="UTF-8" ?>
<!--日志级别以及优先级排序: OFF> FATAL> ERROR> WARN> INFO> DEBUG> TRACE> ALL-->
<!--Configuration后面的 status用于设置 log4j2自身内部的信息输出,可以不设置, 当设置成trace时,可以看到log4j2内部各种详细输出-->
<configuration status="DEBUG">
<!--先定义所有的 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>
Spring5 框架核心容器
支持@Nullable 注解
@Nullable注解可以使用在方法上面,属性上面,参数上面,表示方法返回可以为空,属性值可以为空,参数值可以为空。
- 注解用在方法上面,方法返回值可以为空。
- 注解使用在方法参数里面,方法参数可以为空。
- 注解使用在属性上面,属性值可以为空。
支持函数式风格 GenericApplication Context
函数式风格创建对象,交给 spring 进行管理。
@Test
public void testGenericApplicationContext() {
//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);
}
Spring5 支持整合 JUnit5
整合 JUnit4
测试类使用注解方式完成。
@RunWith(SpringJUnit4ClassRunner.class)
//单元测试框架
@ContextConfiguration("classpath:bean1.xml")
//加载配置文件
public class JTest4 {
@Autowired
private UserService userService;
@Test
public void test1() {
userService.accountMoney();
}
}
整合Junit5
测试类使用注解方式完成。
@ExtendWith(SpringExtension.class)
@ContextConfiguration("classpath:bean1.xml")
public class JTest5 {
@Autowired
private UserService userService;
@Test
public void test1() {
userService.accountMoney();
}
}
使用一个复合注解代替上面2个注解完成整合。
@SpringJUnitConfig(locations = "classpath:bean1.xml")
public class JTest5{
@Autowired
private UserService userService;
@Test
public void test1() {
userService.accountMoney();
}
}
Spring Web Flux
Spring Web Flux 介绍
是 Spring5 添加新的模块,用于 web 开发的,功能和 SpringMVC 类似的,Webflux 使用当前一种比较流程响应式编程出现的框架。
使用传统 web框架,比如 SpringMVC,这些基于 Servlet容器,WebFlux是一种异步非阻塞的框架,异步非阻塞的框架在 Servlet 3.1以后才支持,核心是基于 Reactor的相关 API实现的。