Spring是一个为了简化企业级开发的开源框架。
Spring的特性:
导入jar包
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>5.3.1version>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
<scope>testscope>
dependency>
编写核心配置文件【applicationContext.xml/beans.xml/spring.xml】
配置文件路径:src/main/resources
示例代码
<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="Stu" class="Spring.pojo.Student">
<property name="stuId" value="1">property>
<property name="stuName" value="Sivan">property>
bean>
beans>
使用核心类库
public void test(){
//使用Spring之前
Student student = new Student();
//使用Spring之后
//创建ioc容器
ApplicationContext iocObj =
new ClassPathXmlApplicationContext("applicationContext.xml");
//通过容器对象id获取需要对象。
Student Stu = (Student)iocObj.getBean("Stu",Student.class);
System.out.println(Stu);
}
getBean(String beanId)
:通过beanId获取对象。【但是需要类型转换】getBean(Class clazz)
:通过反射获取对象。【但是容器中有多个相同bean,会报错】getBean(String beanId,Class clazz)
:整合前两种方法。dataSource
的beanIOC:将对象的控制权反转给Spring
BeanFactory接口
IOC容器的基本实现,是Spring的内部使用接口,面向Spring本身。
ApplicationContext接口
BeanFactory的子接口,提供了更多高级特性。面向Spring的使用者。
ConfigurableApplicationContext接口
提供关闭或刷新容器对象的方法。
ClassPathXmlApplicationContext与FileSystemXmlApplicationContext实现类
基于类路径检索xml文件/基于文件系统检索xml文件。
<bean id="Stu2" class="Spring.pojo.Student">
<property name="stuId" value="2">property>
<property name="stuName">
<value>>]]>value>
property>
bean>
使用外部已声明bean的语法:ref
。
级联属性更改数值之后会影响外部声明的bean。(ref中使用的是引用)
示例代码:
<bean id="Dept01" class="Spring.pojo.Dept">
<property name="deptId" value="1">property>
<property name="deptName" value="财务部">property>
bean>
<bean id="Emp01" class="Spring.pojo.Employee">
<property name="id" value="1">property>
<property name="email" value="[email protected]">property>
<property name="lastName" value="WWW">property>
<property name="salary" value="100.00">property>
<property name="dept" ref="Dept01">property>
<property name="dept.deptName" value="研发部">property>
bean>
概念:在bean标签中定义另一个完整的bean。
内部bean不会直接装配到IOC容器中。所以无法从容器中直接获取。
<bean id="Emp02" class="Spring.pojo.Employee">
<property name="id" value="2">property>
<property name="email" value="[email protected]">property>
<property name="lastName" value="XXX">property>
<property name="salary" value="110.00">property>
<property name="dept">
<bean class="Spring.pojo.Dept">
<property name="deptName" value="XXX部门">property>
<property name="deptId" value="1">property>
bean>
property>
bean>
如果传入bean中的属性是集合,就不能简单的用
应该用以下方式:
List集合
<utils:list id="list01">
<ref bean="Emp01">ref>
<ref bean="Emp02">ref>
utils:list>
<bean id="Dept02" class="Spring.pojo.Dept">
<property name="deptId" value="1">property>
<property name="deptName" value="研发部">property>
<property name="empList" ref="list01">property>
bean>
Map集合
<utils:map id="map01">
<entry key="101" value-ref="Emp01">entry>
<entry >
<key><value>102value>key>
<ref bean="Emp02">ref>
entry>
utils:map>
<bean id="EmpMap" class="Spring.pojo.Dept">
<property name="deptId" value="101">property>
<property name="deptName" value="研发部门">property>
<property name="empMap" ref="map01">property>
bean>
调用set方法注入
语法:
使用构造器注入
语法:
使用p名称空间注入
需要先导入:xmlns:p=“http://www.springframework.org/schema/p”
语法:在bean标签中,使用p:XXX。也是使用set方法注入。
示例代码:
<bean id="Stu3" class="Spring.pojo.Student">
<constructor-arg name="stuId" value="3">constructor-arg>
<constructor-arg name="stuName" value="WWW">constructor-arg>
bean>
<bean id="Stu4" class="Spring.pojo.Student" p:stuId="4" p:stuName="XXX" >bean>
以Spring管理Druid数据源为例。
导入jar包
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
<version>1.1.10version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.28version>
dependency>
编写db.properties配置文件
#这里名字最好使用一个前缀,防止与Mysql冲突
db.driverClassName=com.mysql.cj.jdbc.Driver
db.url=jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf-8
db.username=root
db.password=root
编写applicationContext.xml
//加载外部属性文件db.properties
<context:property-placeholder location="classpath:db.properties">context:property-placeholder>
//装配数据源
<bean id="Druid" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${db.driverClassName}">property>
<property name="url" value="${db.url}">property>
<property name="username" value="${db.username}">property>
<property name="password" value="${db.password}">property>
bean>
beans>
测试
ApplicationContext ioc = new ClassPathXmlApplicationContext("ApplicationContext_druid.xml");
DruidDataSource druid = ioc.getBean("Druid", DruidDataSource.class);
System.out.println("druid = " + druid);
DruidPooledConnection connection = druid.getConnection();
System.out.println("connection = " + connection);
实现FactoryBean接口
重写方法【三个】
package factory;
import pojo.Dept;
import org.springframework.beans.factory.FactoryBean;
public class MyFactoryBean implements FactoryBean<Dept> {
/**
* getObject():参数对象创建的方法
* @return
* @throws Exception
*/
@Override
public Dept getObject() throws Exception {
Dept dept = new Dept(101,"研发部门");
//.....
return dept;
}
/**
* 设置参数对象Class
* @return
*/
@Override
public Class<?> getObjectType() {
return Dept.class;
}
/**
* 设置当前对象是否为单例
* @return
*/
@Override
public boolean isSingleton() {
return true;
}
}
装配工厂bean
在applicationContext.xml中使用bean标签装配工厂bean。
测试使用
scope
属性即可① 通过构造器或工厂方法创建bean实例
<bean id="Student" class="Spring.pojo.Student"
init-method="initMethod"
destroy-method="destroyMethod">
<property name="id" value="1">property>
<property name="name" value="Xin">property>
bean>
② 为bean的属性设置值(比如setid
方法)和对其他bean的引用
③ 调用bean的初始化方法
④ bean可以使用了
⑤ 当容器关闭时,若使用ConfigurableApplicationContext
初始化容器,则可以调用bean的销毁方法context.close
。
作用:在调用初始化方法前后对bean进行额外的处理。
实现:
实现BeanPostProcessor接口
重写方法
在容器中装配后置处理器
<bean class="Spring.pojo.processor.Myprocessor">bean>
注意:装配后置处理器会为当前容器中每个bean均装配,不能为局部bean装配后置处理器
在bean标签中添加属性:Autowire即可
byName:对象中属性名称与该属性容器中的beanid进行匹配,如果一致,则自动装配成功
byType:对象中属性类型与该属性容器中的beanclass进行匹配,如果唯一,则自动装配成功
匹配0个:未装配
匹配多个,会报错
expected single matching bean but found 2: deptDao,deptDao2(期望匹配单个bean,但找到两个bean)
<!--将deptDao装配到容器中-->
<bean id="deptDao" class="Spring.dao.impl.DeptImpl"></bean>
<!--自动装配——byName,属性名与该属性的beanid进行匹配。-->
<bean id="deptService" class="Spring.service.impl.DeptServiceImpl" autowire="byName"></bean>
<!-- 自动装配——byType,属性类型与class一致-->
<bean id="deptService2" class="Spring.service.impl.DeptServiceImpl" autowire="byType"></bean>
注意:基于XML方式的自动装配,只能装配非字面量数值
位置:在类的上面标识。
注意:
- Spring本身不区分四个注解【注解本质一样,都是Component】,目的是提高代码可读性。
- Java中的约定:约束(Maven包结构、Java命名规则等)>注解>XML>代码
- 关于beanid:不使用value,默认将类名首字母小写作为beanid。也可以使用value属性/省略value从而设置beanid。
装配对象的四个注解:
@Component(组件):装配普通组件到IOC中。
@Repository(仓库):装配持久化层到IOC中。
@Service(业务):装配业务逻辑层到IOC中。
@Controller(控制):装配控制层/表示层组件到IOC中。
使用注解装配对象的步骤:
使用组件扫描(目的是扫描到注解)
//注意命名空间。
<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-3.0.xsd">
<context:component-scan base-package="Spring">context:component-scan>
beans>
使用注解标识组件(标识的是实现类Impl,因为只有实现类才可以创建对象)
ApplicationContext context =
new ClassPathXmlApplicationContext("applicationContext.xml");
//使用Component注解
Dept dept = context.getBean("dept", Dept.class);
System.out.println("dept = " + dept);
//使用Repository注解
DeptDaoImpl deptdaoimpl = context.getBean("deptdaoimpl", DeptDaoImpl.class);
System.out.println("deptdaoimpl = " + deptdaoimpl);
//使用Service注解
DeptServiceImpl deptserviceimpl = context.getBean("deptserviceimpl", DeptServiceImpl.class);
System.out.println("deptserviceimpl = " + deptserviceimpl);
//使用Controller注解
DeptController deptController = context.getBean("deptController", DeptController.class);
System.out.println("deptController = " + deptController);
依赖关系:比如Service层依赖Dao层的对象。
使用**@Autowired注解**来管理对象之间的依赖关系(装配对象中的属性)。
使用方式:直接注解到属性上。
装配原理:反射机制
装配方式
先按照byType进行匹配(寻找接口的实现类)
匹配1个:匹配成功,正常使用
匹配0个:
默认【@Autowired(required=true)】报错
/*expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
*/
@Autowired(required=false),不会报错
匹配多个
再按照byName进行唯一筛选
筛选成功【对象中属性名称==beanId】,正常使用
筛选失败【对象中属性名称!=beanId】,报如下错误:
//expected single matching bean but found 2: deptDao,deptDao2
@Autowired中required属性
true:表示被标识的属性必须装配数值,如未装配,会报错。
false:表示被标识的属性不必须装配数值,如未装配,不会报错。
@Qualifier注解
@Value注解
组件扫描:扫描自己写的注解,可以让程序识别。
默认情况下会扫描包下面的所有包。如果想要扫描100个包中个个别几个,可以使用包含扫描/排除扫描。
use-default-filters="false"
【关闭当前包及其子包的扫描】
<context:component-scan base-package="Spring" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
<context:include-filter type="assignable" expression="Spring.service.impl.DeptServiceImpl"/>
<context:exclude-filter type="assignable" expression="Spring.controller.DeptController"/>
context:component-scan>
完全取消xml配置过程。
创建配置类
在配置类上添加注解
示例代码:
@ComponentScan(basePackages = "Spring")
@Configuration
public class SpringConfig {
}
使用 AnnotationConfigApplicationContext 创建容器对象
示例代码:
@Test
public void test(){
ApplicationContext context =
new AnnotationConfigApplicationContext(SpringConfig.class);
Object deptdaoimpl = context.getBean("deptdaoimpl");
System.out.println("deptdaoimpl = " + deptdaoimpl);
}
Spring提供了对Junit的集成,所以可以在测试类中直接注入IOC容器中的对象。
导入jar包支持
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-testartifactId>
<version>5.3.1version>
<scope>testscope>
dependency>
指定Spring配置文件路径/Config类【使用@ContextConfiguration】
指定Spring环境下的Junit4 的运行器【使用@RunWith】
示例代码:
@ContextConfiguration(classes = SpringConfig.class)
@RunWith(SpringJUnit4ClassRunner.class)
public class TextJunit {
@Autowired
DeptDao deptdao;
@Test
public void test(){
deptdao.addDept(new Dept());
}
}
代理模式:比如房屋中介、房屋App等都是代理。
我们(目标对象)与中介(代理对象)不能相互转换,因为是平级关系。
需求:实现【加减乘除】计算器类
实现后发现问题如下
总结:我们不期望在核心业务代码中书写日志代码。
此时:使用代理模式解决问题:
【先将日志代码横向提取到日志类中,再动态织入回到业务代码中】
注意:代理对象与实现类【目标对象】是“兄弟”关系,不能相互转换
实现方式
基于接口实现动态代理的具体步骤:
创建类(比如MyProxy
)【为了创建代理对象的工具类】
提供属性【目标对象,也就是实现类】
提供方法(比如getProxyObject()
)【为目标对象创建代理对象】
实现动态代理关键步骤
提供有参构造器【避免目标对象为空】
package com.atguigu.beforeaop;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* MyProxy为获取代理对象的工具类。
*/
public class MyProxy {
/**
* 目标对象【目标客户】
*/
private Object target;
public MyProxy(Object target){
this.target = target;
}
/**
* 获取目标对象的,代理对象
* @return
*/
public Object getProxyObject(){
Object proxyObj = null;
/**
第一个参数:类加载器【ClassLoader loader】,目标对象的类加载器
第二个参数:目标对象实现接口:Class>[] interfaces,目标对象实现所有接口
第三个参数:InvocationHandler (动态织入的关键方法)
*/
//创建类加载器
ClassLoader classLoader = target.getClass().getClassLoader();
//代理对象要知道目标对象实现了什么样的接口
Class<?>[] interfaces = target.getClass().getInterfaces();
//创建代理对象
proxyObj = Proxy.newProxyInstance(classLoader, interfaces, new InvocationHandler() {
//执行invoke()实现动态织入效果
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//获取方法名【目标对象】
String methodName = method.getName();
//执行目标方法之前,添加日志
MyLogging.beforeMethod(methodName,args);
//获取目标对象的目标方法。
Object rs = method.invoke(target, args);
//执行目标方法之后,添加日志
MyLogging.afterMethod(methodName,rs);
return rs;
}
});
return proxyObj;
}
//不推荐以下方法:使用匿名内部类
// class invocationImpl implements InvocationHandler{
// }
}
@Test
public void testBeforeAop(){
// int add = calc.add(1, 2);
// System.out.println("add = " + add);
//目标对象
Calc calc = new CalcImpl();
//代理工具类
MyProxy myProxy = new MyProxy(calc);
//获取代理对象,与目标对象CalcImpl不能相互转换,两者是兄弟关系。
Calc calcProxy = (Calc)myProxy.getProxyObject();
//使用代理对象执行方法
// int add = calcProxy.add(1, 2);
int div = calcProxy.div(2, 1);
}
导入相关jar包
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-aspectsartifactId>
<version>5.3.1version>
dependency>
配置文件
<context:component-scan base-package="com.atguigu">context:component-scan>
<aop:aspectj-autoproxy>aop:aspectj-autoproxy>
切面类(MyLogging类)添加注解
@Component //标识当前类为一个组件。
@Aspect //将当前类标识为切面类(非核心业务提取类)。
在切面类中添加通知注解
@Component
@Aspect
public class MyLogging {
@Before(value = "execution(public int Spring.AOP.CalcImpl.add(int ,int))")
public void beforeMethod(JoinPoint joinPoint){
//获取当前方法名称
String name = joinPoint.getSignature().getName();
//获取方法形参
Object[] args = joinPoint.getArgs();
System.out.println("当前调用"+ name + "方法,参数是"+ Arrays.toString(args));
}
}
测试
@Test
public void test1(){
ApplicationContext context =
new ClassPathXmlApplicationContext("applicationContext.xml");
Calc calc = context.getBean("calc", Calc.class);
//错误的,代理对象不能转换为目标对象【代理对象与目标对象是兄弟关系】
//CalcImpl calc = context.getBean("calc", CalcImpl.class);
calc.add(1,2);
}
AOP更针对某一个方法,进行横向的扩展。比如添加日志等。
语法:@Before(value = "execution(权限修饰符 返回值类型 包名.类名.方法名(参数类型))")
通配符
[ * ]:代表任意权限修饰符+返回值类型 和 任意包名+类名+方法名。
[ … ] :代表任意参数类型及参数个数。
通配符常用的方法是:
@Before(value = "execution(* Spring.AOP.CalcImpl.* (..))")
重用切入点表达式
@Pointcut("execution(* Spring.AOP.CalcImpl.* (..))")
public void myPoint(){
}
@After("myPoint()")
public void AfterMethod(JoinPoint joinPoint){
System.out.println("后置");
}
JoinPont【切入点对象】
作用:
获取方法名称
//获取方法签名【方法签名=方法名+参数列表】
joinPoint.getSignature();
//获取方法名称
String methodName = joinPoint.getSignature().getName();
获取参数
Object[] args = joinPoint.getArgs();
指定方法:切入点表达式设置位置
前置通知 @Before
后置通知 @After
返回通知 @AfterReturning
执行时机:指定方法返回结果时执行,【如目标方法中有异常,不执行】
注意事项:@AfterReturning中returning属性与入参中参数名一致
示例代码:
@AfterReturning(value = "myPoint()",returning = "rs")
public void AfterReturning(JoinPoint joinPoint,Object rs){
//获取当前方法名称
String name = joinPoint.getSignature().getName();
//获取方法形参
Object[] args = joinPoint.getArgs();
System.out.println("【返回通知】当前调用"+ name + "方法,返回结果是"+ rs);
}
异常通知 @AfterThrowing
执行时机:指定方法出现异常时执行,【如目标方法中没出现异常,不执行】
注意事项:@AfterThrowing中throwing属性与入参中参数名一致
示例代码
@AfterThrowing(value = "myPoint()",throwing = "ex")
public void AfterThrowing(JoinPoint joinPoint , Exception ex){
//获取当前方法名称
String name = joinPoint.getSignature().getName();
//获取方法形参
Object[] args = joinPoint.getArgs();
System.out.println("【异常通知】当前调用"+ name + "方法,出现的异常是"+ ex);
}
总结
环绕通知 @Around
该通知可以整合前四个通知。
注意:
proceed()
方法示例代码
@Around("myPoint()")
public Object AroundMethod(ProceedingJoinPoint pjp){
String name = pjp.getSignature().getName();
Object[] args = pjp.getArgs();
//定义方法返回值
Object rs = null;
try {
//前置通知
System.out.println("【前置通知】当前调用"+ name + "方法,参数是"+ Arrays.toString(args));
//触发目标对象的方法
rs = pjp.proceed();
//返回通知
System.out.println("【返回通知】当前调用"+ name + "方法,返回结果是"+ rs);
} catch (Throwable ex) {
//异常通知
System.out.println("【异常通知】当前调用"+ name + "方法,出现的异常是"+ ex);
}finally {
//后置通知
System.out.println("【后置通知】当前调用"+ name + "方法,参数是"+ Arrays.toString(args));
}
return rs;
}
两个切面类中如果都存在前置等切面,则可以使用@Order来定义切面类的优先级。
@Order(value = index),其中index值越小,优先级越高
@Component
@Aspect
@Order(value = 1)
public class MyValidate {}
============================
@Component
@Aspect
@Order(2)
public class MyLogging {}
在一些老的项目中,可能没有使用基于注解配置,还在使用XML的方式进行配置切面类。
<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 https://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="calculator" class="com.atguigu.spring.aop.xml.CalculatorImpl">bean>
<bean id="loggingAspect" class="com.atguigu.spring.aop.xml.LoggingAspect">bean>
<aop:config>
<aop:pointcut id="pointCut"
expression="execution(* com.atguigu.spring.aop.xml.Calculator.*(..))"/>
<aop:aspect ref="loggingAspect">
<aop:before method="beforeAdvice" pointcut-ref="pointCut">aop:before>
<aop:after-returning method="returningAdvice" pointcut-ref="pointCut" returning="result">aop:after-returning>
<aop:after-throwing method="throwingAdvice" pointcut-ref="pointCut" throwing="e">aop:after-throwing>
<aop:after method="afterAdvice" pointcut-ref="pointCut">aop:after>
<aop:around method="aroundAdvice" pointcut-ref="pointCut">aop:around>
aop:aspect>
aop:config>
beans>
Spring提供的jdbcTemplate是一个小型的持久化层框架,可以简化jdbc代码。
//重要的是导入该jar包。
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-ormartifactId>
<version>5.3.1version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>5.3.1version>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
<scope>testscope>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
<version>1.1.10version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.28version>
dependency>
编写配置文件
db.properties:设置连接数据库属性
applicationContext.xml:spring配置文件
示例代码
db.properties
db.driver=com.mysql.cj.jdbc.Driver
db.url=jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf-8
db.username=root
db.password=root
applicationContext_JdbcTemplate.xml
<context:property-placeholder location="classpath:db.properties">context:property-placeholder>
<bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${db.driver}">property>
<property name="url" value="${db.url}">property>
<property name="password" value="${db.password}">property>
<property name="username" value="${db.username}">property>
bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="druidDataSource">property>
bean>
装配自己内部的bean可以使用注解,但是第三方的bean要使用xml去装配,比如jdbcTemplate。因为装配类需要在类上面去写注解。
使用核心类库(API调用)
JdbcTemplate默认:自动提交事务,不像Mybatis一样手动提交事务。
ApplicationContext context =
new ClassPathXmlApplicationContext("applicationContext_Jdbc.xml");
JdbcTemplate jdbcTemplate = context.getBean("jdbcTemplate", JdbcTemplate.class);
String sql = "insert into tbl_employee(last_name,email,salary,dept_id) values(?,?,?,?)";
List<Object[]> list = new ArrayList<>();
list.add(new Object[]{"qq0","qq.com",123,123});
list.add(new Object[]{"qq1","qq.com",123,123});
list.add(new Object[]{"qq2","qq.com",123,123});
list.add(new Object[]{"qq3","qq.com",123,123});
//演示批量添加操作。
jdbcTemplate.batchUpdate(sql,list);
//查询单个数值
String sql = "select count(1) from tbl_employee";
Integer integer = jdbcTemplate.queryForObject(sql, Integer.class);
System.out.println("integer = " + integer);
//查询单个对象
String sql = "select id,last_name,email,salary from tbl_employee where id = ?";
//创建RowMapper
RowMapper<Employee> rowMapper = new BeanPropertyRowMapper<>(Employee.class);
Employee employee = jdbcTemplate.queryForObject(sql, rowMapper, 1);
System.out.println("employee = " + employee);
//查询多个对象
String sql = "select id,last_name,email,salary from tbl_employee";
RowMapper<Employee> rowMapper = new BeanPropertyRowMapper<>(Employee.class);
List<Employee> employeeList = jdbcTemplate.query(sql, rowMapper);
Service层依赖Dao层
Dao层依赖JdbcTemplate
示例代码:
//text:
/**
* @Author 不知名网友鑫
* @Date 2022/10/2
**/
@ContextConfiguration(locations = "classpath:applicationContext_Jdbc.xml")
@RunWith(SpringJUnit4ClassRunner.class)
public class TestDaoService {
@Autowired
DeptService deptService;
@Test
public void test(){
List<Dept> depts = deptService.selectDeptS();
System.out.println("depts = " + depts);
}
}
//DeptService
/**
* @Author 不知名网友鑫
* @Date 2022/10/2
**/
@Service("deptService")
public class DeptServiceImpl implements DeptService{
@Autowired
DeptDao deptDao;
@Override
public List<Dept> selectDeptS() {
List<Dept> depts = deptDao.selectDept();
return depts;
}
}
//DeptDao
/**
* @Author 不知名网友鑫
* @Date 2022/10/2
**/
@Repository("deptDao")
public class DeptDaoImpl implements DeptDao{
@Autowired
JdbcTemplate jdbcTemplate;
@Override
public List<Dept> selectDept() {
String sql = "select dept_id,dept_name from tbl_dept";
RowMapper<Dept> rowMapper = new BeanPropertyRowMapper<>(Dept.class);
List<Dept> query = jdbcTemplate.query(sql, rowMapper);
return query;
}
}
回顾事务:
- 事务四大特征【ACID】
- 原子性:事务是最小单元,不可分割。
- 一致性:事务要么同时提交,要么同时回滚。
- 隔离性:事务与事务彼此隔离,不能有关联。多个事务并发执行不会互相干扰。
- 持久性:事务对数据1的修改应该被写在持久化容器中。
- 事务三种行为
- 开启事务:connection.setAutoCommit(fasle);
- 提交事务:connection.commit();
- 回滚事务:connection.rollback();
案例:去结账。
具体步骤:1. 生成订单 2. 生成订单详情 3. 更改库存&销量 4. 清空购物车
如果其中一步出错,我们希望整个流程一起回滚,而不是提交部分,此时我们需要进行事务管理。
编程式事务管理【传统事务管理】
问题:事务管理的代码与核心业务代码相耦合。
声明式事务管理【使用AOP管理事务】
方式:先横向提取事务管理代码,再动态织入。
不用事务管理代码,发现:同一个业务中,会出现局部成功及局部失败的现象【不正常】
导入Jar包,添加AspectJ的jar包。
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-aspectsartifactId>
<version>5.3.1version>
dependency>
编写配置文件
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="druidDataSource">property>
bean>
<tx:annotation-driven>tx:annotation-driven>
在需要事务管理的业务方法上,添加注解**@Transactional**
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void purchase(String username, String isbn) {
//查询book价格
Integer price = bookshopDao.findBookPriceByIsbn(isbn);
//修改库存
bookshopDao.updateBookStock(isbn);
//修改余额
bookshopDao.updateUserAccount(username, price);
}
@Transactional注解属性
当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。
Propagation常用属性:
REQUIRED
传播行为:
当 bookService 的 purchase()方法被另一个事务方法 checkout()调用时,它默认会在现有的事务内运行。因此在 checkout()方法的开始和终止边界内只有一个事务。
如果余额不足,就一本书也买不了。
REQUIRES_NEW
传播行为:
表示该方法必须启动一个新事务,并在自己的事务内运行。如果有事务在运行,就应该先挂起它。
假设现在有两个并发的事务:Transaction01和Transaction02。
脏读【读取到了未提交的事务】:
①Transaction01 将某条记录的 AGE 值从 20 修改为 30,但是没有提交。
②Transaction02 读取了 Transaction01 更新后的值:30。
③随后Transaction01 回滚,AGE 值恢复到了 20。
④Transaction02 读取到的 30 就是一个无效的值。
不可重复读 【多次从一个字段中读取到的数据不相同】
①Transaction01 读取了 AGE 值为 20。
②Transaction02 将 AGE 值修改为 30,并且提交数据。
③Transaction01 再次读取 AGE 值为 30,和第一次读取不一致。
幻读【多次从一个表中读取的行不相同】
①Transaction01 读取了 STUDENT 表中的一部分数据。
②Transaction02 向 STUDENT 表中插入了新的行。
③Transaction01 读取了 STUDENT 表时,多出了一些行。
数据库系统必须具有隔离并发运行各个事务的能力,使它们不会互相影响。
隔离级别越高,数据一致性就越好,但并发性越弱。一个事务与其他事务之间的隔离等级【1,2,4,8】
隔离等级
类型:int,单位:second。
默认值:-1【未设置强制回滚】
设置超时时间,到达指定时间后,会强制事务回滚。
事务只读【readonly】
一般事务方法中,只有查询操作时,才将事务设置为只读。
事务回滚【rollbackFor/noRollbackFor】
遇见回滚/不回滚的异常类。
示例代码:
//当前事务传播行为是自己属于一个事务。
@Transactional(propagation = Propagation.REQUIRES_NEW,
timeout = 1,
noRollbackFor = ArithmeticException.class)
public void purchase(String username, String isbn) {
//查询book价格
Integer price = bookshopDao.findBookPriceByIsbn(isbn);
//测试事务超时
// try {
// Thread.sleep(5000);
// } catch (InterruptedException e) {
// throw new RuntimeException(e);
// }
//修改库存
bookshopDao.updateBookStock(isbn);
//修改余额
bookshopDao.updateUserAccount(username, price);
//演示遇到算数异常时,事务不回滚
int i = 1/0;
}
<aop:config>
<aop:pointcut expression="execution(* com.atguigu.tx.component.service.BookShopServiceImpl.purchase(..))" id="txPointCut"/>
<aop:advisor advice-ref="myTx" pointcut-ref="txPointCut"/>
aop:config>
<tx:advice id="myTx" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="find*" read-only="true"/>
<tx:method name="get*" read-only="true"/>
<tx:method name="purchase"
isolation="READ_COMMITTED"
no-rollback-for="java.lang.ArithmeticException,java.lang.NullPointerException" propagation="REQUIRES_NEW"
read-only="false"
timeout="10"/>
tx:attributes>
tx:advice>
以@Nullable 为例
位置:可以书写在方法&属性&参数前面。
作用:表示当前方法或属性可以为空,消除了空指针异常。
导入jar包
<dependency>
<groupId>org.apache.logging.log4jgroupId>
<artifactId>log4j-slf4j-implartifactId>
<version>2.11.2version>
<scope>testscope>
dependency>
编写配置文件(不需要自己编写)
log4j2.xml
日志级别以及优先级排序:OFF > FATAL > WHAR > INFO > DEBUG > TRACE > ALL
高级别会打印低级别的内容。
导入jar包【注意:将Junit4的jar包删除】
<dependency>
<groupId>org.junit.jupitergroupId>
<artifactId>junit-jupiter-apiartifactId>
<version>5.7.2version>
<scope>testscope>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-testartifactId>
<version>5.3.1version>
dependency>
使用注解进行整合。
//方式一:
@ContextConfiguration(locations = "classpath:applicationContext.xml")
@ExtendWith(SpringExtension.class)
//方式二:
@SpringJUnitConfig(locations = "classpath:applicationContext.xml")