文章目录
06_SpringAop使用和JdbcTemplate
SpringAOP的使用
AOP相关术语
- Joinpoint(连接点) (程序中所有方法的方法前、方法后、异常时等时机都是连接点)
Spring支持在方法调用前、方法调用后、方法抛出异常时这些程序执行点织入增强。
我们知道黑客攻击系统需要找到突破口,没有突破口就无法进行攻击,从这一角度上来说,AOP是一个黑客(因为它要向目前类中嵌入额外的代码逻辑),连接点就是AOP向目标类打入楔子的候选点。
- Pointcut(切入点)(定位你感兴趣的方法)
一个类中可以有很多个方法,每个方法又有多个Joinpoint,在这么多个方法中,如何定位到自己感兴趣的方法呢?靠的是切点
注意:切点只定位到某个方法上,所以如果希望定位到具体连接点上,还需要提供方位信息
比如:如果把一个方法理解成数据表中的一条记录的话,那么切入点就好比你select语句的where条件 ,就可以定位到你感兴趣的方法
-
Advice(通知/增强)
增强的第一层意思就是你的横切逻辑代码(增强逻辑代码)
在Spring中,增强除了用于描述横切逻辑外,包含一层意思就是横切逻辑执行的方位信息。
刚刚说了切点只能定位到方法,在进一步使用方位信息就可以定位到我们感兴趣的连接点了(方法调用前、方法调用后还是方法抛出异常时等)。
**增强:横切逻辑+**方位信息(定位我们感兴趣的连接点)
-
Weaving(织入) 形象的描述了增强横切逻辑的整个过程
织入是将增强逻辑/横切逻辑添加到目标类具体连接点上的过程,AOP像一台织布机,将目标类、增强或者引介通过AOP(其实就是动态代理技术)这台织布机天衣无缝地编织到一起。
Spring采用动态代理织入。
-
Proxy(代理)代理对象
一个类被AOP织入增强后,就产出了一个结果类,它是融合了原类和增强逻辑的代理类。
-
Aspect(切面)
切面由切点和增强组成。
切面=切点+增强
=切点+方位信息+横切逻辑
=感兴趣的连接点+横切逻辑
最终切面完成:把横切逻辑织入到哪些方法的方法前/后等
本质:把横切逻辑增强到连接点(切点和方位信息都是为了确定连接点)上
AOP相关术语描述的本质:把横切逻辑代码织入到我们感兴趣的那些方法中的感兴趣的连接点位置上
基于xml的AOP配置
l 需求:在Service层代码的不同方法的不同连接点JoinPoint织入日志
把Account表的service层进行crud模拟(dao层就不需要了)
l 引入POM坐标
<!--使用Spring的AOP功能-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<!--SpringAop功能依赖的jar-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.9</version>
</dependency>
applicationContext.xml配置
切入点表达式说明(切入点表达式语法使用的是AspectJ框架的表达式语法)
XML配置及四种常用Advice通知类型(即四种方位)
aop:before :配置前置增强,指定增强的方法在切入点方法之前执行
aop:after-returning:配置后置增强,切入点方法正常执行之后,此处等同于try{ …}catch(){}finally{},红色点标注的时机
aop:after-throwing:配置异常通知,切入点方法执行产生异常后执行,它和后置增强只能执行一个,等同于catch中的时机
aop:after:配置最终增强,无论切入点方法执行时是否有异常,它都会在其后面执行,等同于finally中的执行时机
环绕通知:它是spring为我们提供的一种可以在代码中手动控制增强方法何时执行的方式,灵活度比较高,设置可以控制原业务逻辑是否执行,其他四种通知类型的原有业务逻辑,肯定执行!
注意:通常情况下,环绕通知都是独立使用的,不要和上面的四种通知类型混合使用
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
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
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.ujiuye.spring"/>
<!--配置AOP内容都放置在aop:config标签下-->
<!--横切逻辑bean-->
<bean id="logUtil" class="com.ujiuye.spring.utils.LogUtil"/>
<aop:config>
<!--aop主要就是配置切面,所以aop:aspect
切面=切点+方位信息+横切逻辑
ref:指向横切逻辑
<aop:before>等标签已经表明方位信息
切点:使用pointcut进行定义
-->
<aop:aspect ref="logUtil">
<!--aop:pointcut单独配置切入点表达式,供引用-->
<aop:pointcut id="pt1" expression="execution(* com.ujiuye.spring.service.*.*(..))"/>
<!--<aop:before method="printBeforeMethod" pointcut-ref="pt1"/>
<aop:after-returning method="printAfterReturn" pointcut-ref="pt1"/>
<aop:after-throwing method="printAfterThrowing" pointcut-ref="pt1"/>
<aop:after method="printAfterMethod" pointcut-ref="pt1"/>-->
<aop:around method="printRound" pointcut-ref="pt1"/>
</aop:aspect>
</aop:config>
</beans>
基于注解的AOP配置
l 半xml半注解形式
n 在xml配置文件中开启AOP注解开关
<!--aop注解驱动的开关-->
<aop:aspectj-autoproxy>
在横切逻辑类上配置
package com.ujiuye.spring.utils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class LogUtil {
@Pointcut("execution(* com.ujiuye.spring.service.*.*(..))")
public void pt1() {
}
/* *//**
* 方法执行之前打印
*//*
@Before("pt1()")
public void printBeforeMethod() {
System.out.println("方法执行之前打印");
}
*//**
* 方法结束时打印
*//*
@After("pt1()")
public void printAfterMethod() {
System.out.println("方法执行之后(结束)打印");
}
*//**
* 方法异常时打印
*//*
@AfterThrowing("pt1()")
public void printAfterThrowing() {
System.out.println("方法异常时打印");
}
*//**
* 方法正常执行(没有抛出异常)时打印
*//*
@AfterReturning("pt1()")
public void printAfterReturn() {
System.out.println("方法正常执行(返回)打印");
}*/
/**
* 环绕通知,灵活度比较高,可以手动控制在业务逻辑之前、之后、异常时等连接点执行逻辑
* 也可以控制原有业务逻辑是否执行,通过直接在参数中声明形参ProceedingJoinPoint即可,类似于动态代理中的method.invoke()
* @param proceedingJoinPoint
* @return
*/
@Around("pt1()")
public Object printRound(ProceedingJoinPoint proceedingJoinPoint) {
Object result = null;
try {
System.out.println("环绕通知的之前执行");
result = proceedingJoinPoint.proceed();
System.out.println("环绕通知的正常执行时执行");
} catch (Throwable throwable) {
throwable.printStackTrace();
System.out.println("环绕通知的异常执行时执行");
}
System.out.println("环绕通知方法结束时执行");
return result;
}
}
l
全注解模式下注解配置开关
@Configuration
@ComponentScane({"com.test.spring"})
@EnableAspectJAutoProxy
public class SpringConfig{}
学习中的注意事项
AOP的使用场景
AOP的应用场景往往是很有限的,它一般只适合于那些具有横切逻辑的应用场合:如性能监测、访问控制、事务管理以及日志记录等。不过,这丝毫不影响AOP作为一种新的软件开发思想在软件开发领域所占有的地位。
开发阶段(我们完成)
编写核心业务代码
大部分程序员来做,要求熟悉业务需求。
抽取公用代码制作成通知,进行AOP配置
一般由专门的AOP编程人员来做
JdbcTemplate数据库操作工具的使用
Spring框架提供的一个数据库操作工具,使用方法和Dbutils非常相似
Dbutils | JdbcTemplate |
---|---|
核心对象QueryRunner | 核心对象JdbcTemplate |
update接口(增、删、改) | update接口(增、删、改),操作过程和方式和dbutils一样 |
query接口查询 | queryForObject(单个值/单个对象) |
query接口(返回集合) |
Dbutils返回单个对象使用BeanHandler封装数据,返回list集合使用BeanListHandler封装数据。BeanHandler和BeanListHandler都是ResultsetHandler接口的实现类
在JdbcTemplate中,封装数据也提供了接口RowMapper,但是没有给你实现类,只能我们自己去实现这个接口,告诉工具结果集封装规则
入门使用
<!--jdbcTemplate使用的jar-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<!--事务相关的jar,往往和jdbc一起引入-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
import com.ujiuye.spring.pojo.Account;
import org.junit.Before;
import org.junit.Test;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
public class JdbcTemplateTest {
private JdbcTemplate jdbcTemplate;
@Before
public void before() {
// 实例化JdbcTemplate工具的核心对象
jdbcTemplate = new JdbcTemplate(dataSource);
}
/**
* jdbcTemplate工具之insert操作
*/
@Test
public void testSaveAccount() {
String sql = "insert into account(name,money) values(?,?)";
int count = jdbcTemplate.update(sql,"柳岩",100f);
}
/**
* jdbcTemplate工具之update操作
*/
@Test
public void testUpdateAccountById() {
String sql = "update account set name=?,money=? where id=?";
int count = jdbcTemplate.update(sql,"唐嫣",1000f,4);
System.out.println(count);
}
/**
* jdbcTemplate工具之delete操作
*/
@Test
public void testDeleteAccountById() {
String sql = "delete from account where id=?";
int count = jdbcTemplate.update(sql,4);
System.out.println(count);
}
/**
* jdbcTemplate工具之查询单个值(总记录数)
*/
@Test
public void testQueryAccountCount() {
String sql = "select count(id) from account";
/*
返回单个值使用queryForObject接口
param1:sql语句
param2:返回值的数据类型
*/
long count = jdbcTemplate.queryForObject(sql,long.class);
System.out.println("account表总记录数:" + count);
}
/**
* jdbcTemplate工具之查询单个值(字符串类型)(使用queryForObject接口)
*/
@Test
public void testQueryAccountNameById() {
String sql = "select name from account where id=?";
String name = jdbcTemplate.queryForObject(sql,String.class,1);
System.out.println(name);
}
/**
* jdbcTemplate工具之查询单个对象(使用queryForObject接口)
*/
@Test
public void testQueryAccountById() {
String sql = "select id,name,money from account where id=?";
Account account = jdbcTemplate.queryForObject(sql, new AccountRowMapper(), 1);
System.out.println(account);
}
/**
* jdbcTemplate工具之查询返回对象集合(使用query接口)
*/
@Test
public void testQueryAccountList() {
String sql = "select id,name,money from account";
List<Account> list = jdbcTemplate.query(sql, new AccountRowMapper());
if(list != null && list.size() > 0) {
for (int i = 0; i < list.size(); i++) {
Account account = list.get(i);
System.out.println(account);
}
}
}
/**
* AccountRowMapper内部类是对RowMapper接口的实现
* 在查询单个对象或者对象结合的时候封装数据使用
*/
public class AccountRowMapper implements RowMapper<Account>{
@Override
public Account mapRow(ResultSet rs, int rowNum) throws SQLException {
Account account = new Account();
account.setId(rs.getInt("id"));
account.setName(rs.getString("name"));
account.setMoney(rs.getFloat("money"));
return account;
}
}
}
用于dao层开发
l
需求:开发service层和dao层,service层调用dao层,在dao层中使用JdbcTemplate实现Account表的Crud(半xml半注解形式实现)
定于JdbcTemplate为普通Bean
普通玩法:和Dbutils一样,在Spring配置文件中定义JdbcTmeplate的bean对象
<!--JdbcTemplate工具-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
dao层使用JdbcTmeplate对象,直接注入使用
package com.ujiuye.spring.dao;
import com.ujiuye.spring.pojo.Account;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Repository;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
@Repository
public class AccountDaoImpl implements AccountDao {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public void saveAccount(Account account) throws Exception {
String sql = "insert into account(name,money) values (?,?)";
jdbcTemplate.update(sql,account.getName(),account.getMoney());
}
@Override
public void updateAccountById(Account account) throws Exception {
String sql = "update account set name=?,money=? where id=?";
jdbcTemplate.update(sql,account.getName(),account.getMoney(),account.getId());
}
@Override
public void deleteAccountById(Integer id) throws Exception {
String sql = "delete from account where id=?";
jdbcTemplate.update(sql,id);
}
@Override
public Account queryAccountById(Integer id) throws Exception {
String sql = "select id,name,money from account where id=?";
return jdbcTemplate.queryForObject(sql,new AccountRowMapper(),id);
}
@Override
public List<Account> queryAccountList() throws Exception {
String sql = "select id,name,money from account";
return jdbcTemplate.query(sql,new AccountRowMapper());
}
public class AccountRowMapper implements RowMapper<Account>{
@Override
public Account mapRow(ResultSet rs, int rowNum) throws SQLException {
Account account = new Account();
account.setId(rs.getInt("id"));
account.setName(rs.getString("name"));
account.setMoney(rs.getFloat("money"));
return account;
}
}
}
Spring的声明式事务控制
l 关于编程式事务和声明式事务
编程式事务:在业务代码中添加事务控制代码,这样的事务控制机制,叫做编程式事务
声明式事务:通过xml配置或者注解方式就达到事务控制的目的,叫做声明式事务
关于事务
l 事务基本特性(ACID,是针对单个事务的一个完美状态)
n 原子性:一个事务内的操作要成功都成功,要失败都失败。比如转账案例,转账和到账要么同时成功要么同时失败。
n 一致性:一致性和原子性描述的是同一件事,只不过角度不一样。原子性是从一个事务内的操作的角度来说的,要成都成要失败都失败。一致性是从数据的角度来说的,比如转账,转账的初始数据状态(1000,1000),现在转100块,转账完成后,对外来说,数据状态要么(900,1100),要么是(1000,1000),不能够出现(900,1000)等中间状态
n 隔离性:比如事务1给员工涨工资2000元,但是事务1尚未提交事务,事务2查询工资,发现工资涨了2000块。这就是脏读(读到了未提交的数据)
n 持久性:事务一旦提交即生效,即使数据库服务器宕机,那么重启之后数据也应该是事务提交之后的状态,不会是之前的状态
l 事务并发问题
n 脏读
u 场景
财务人员发起事务1,给员工A涨了1w块钱,但是尚未提交事务。此时员工A发起了事务2查询工资,发现涨了1w块钱。
n 幻读(针对insert和delete操作)
u 场景
l 事务1查询所有工资为1w的员工的总数,查出来10个,此时事务尚未关闭。
l 事务2,由财务人员发起,新来两个员工,工资也是1w,向数据表中插入了两条数据,并且提交事务
l 事务1再次查询工资为1w的员工个数,发现有12个。见鬼了
n 不可重复读(针对update操作)
u 场景
l 员工A发起事务1,查询工资,工资为1w块钱,此时事务尚未关闭
l 财务人员发起了事务2给员工A涨了2000块钱,并且提交事务
l 员工A通过事务1再次查询工资,发现工资为1.2w,原来的1w那个数据读不到了,叫做不可重复读。
l 事务隔离级别(解决是事务并发问题的)
n 读未提交(极端):Read Uncommited,读到了未提交的数据,什么并发问题也解决不了。不要采取这种方案
n 读已提交:Read Committed:解决脏读问题,解决不了幻读和不可重复读的问题(因为幻读和不可重复读问题的造成,本身就是已提交事务造成的)
n 可重复读:Read Repeatable,解决脏读和不可重复读的问题。解决不了幻读问题,因为可重复读针对的是update操作,在可重复读机制下,会对要update的语句进行加锁,加锁状态下其他事务无法修改。但是其他事务可以对表新增和删除
n 串行化(极端):Serializable,事务一个个来,是最安全的隔离机制,但是效率较低,比如ATM机。
n 默认,数据库的默认,mysql默认的隔离机制是可重复读,Oracle的默认隔离机制是读已提交
l 事务传播行为
事务往往在service层进行控制,如果出现service层方法A调用了另外一个service层方法B,A和B方法本身都已经被添加了事务控制,那么A调用B的时候,就需要进行事务的一些协商,这就叫做事务的传播行为。
A调用B,我们站在B的角度来观察来定义事务的传播行为
声明式事务配置(xml或者注解)
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.9</version>
</dependency>
基于xml
Spring包容性很强,对dao层很多技术都会提供支持
jdbc:connection.commit()
mybatis:sqlSession.commit()
hibernate:session.commit();
Spring针对上述众多的事务调用方式,为了一统江湖,定义顶层接口(开启事务、提交事务、回滚事务等)
针对不同的技术体系,封装不同的事务管理实现类
事务管理器类,就是关于事务的横切逻辑,我们使用jdbc/jdbcTemplate/mybatis使用的是Spring提供的DataSourceTransactionMannager事务管理类,这个类就如同自定义AOP中开发的TransactionMannager类
注意:
Spring DataSourceTransactionManager事务管理器仅jdbc/Spring JdbcTemplate工具(和DButils使用很相似)/Mybatis。不支持Dbutils****的事务管理
**事务控制本质:**将横切逻辑(DataSourceTransactionMannager,此处的横切逻辑就是事务的控制逻辑)增强到感兴趣的方法上,进行事务控制
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
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
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
">
<context:component-scan base-package="com.ujiuye.spring"/>
<context:property-placeholder location="classpath:db.properties"/>
<!--定义jdbcTemplate对象-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--Spring的声明式事务是基于AOP技术实现
横切逻辑单独定义为一个bean
事务管理器底层还需要数据库连接池的支持
-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!--tx:attributes允许我们对事务细节进行配置-->
<tx:attributes>
<!--CRUD中只有查询是只读事务,其他都是非只读事务
所以在这里可以首先配置所有方法都是非只读事务
然后单独配置查询方法为只读事务,覆盖上面的配置
这样,简化配置量
read-only:是否只读
propagation:事务传播行为
isolation:事务隔离级别,默认defalut
timeout:超时时间,-1没有限制,单位秒
rollback-for:出现什么异常的时候回滚事务,往往不需要配置,有异常就回滚
no-rollback-for:出现什么异常的时候不回滚事务,往往不需要配置,有异常就回滚
-->
<tx:method name="*" read-only="false" propagation="REQUIRED" isolation="DEFAULT" timeout="-1" />
<!--查询操作为只读事务即可,事务传播行为为SUPPORTS,有事务就用,没有就不用-->
<tx:method name="query*" read-only="true" propagation="SUPPORTS"/>
</tx:attributes>
</tx:advice>
<aop:config>
<!--Spring声明式事务使用aop:advisor替代aop中的aop:aspect
至于方位信息不需要指定了(对于事务来说)
-->
<aop:advisor advice-ref="txAdvice" pointcut="execution(* com.ujiuye.spring.service.*.*(..))"/>
</aop:config>
</beans>
基于注解
l 半xml半注解
使用@Transactional注解,同时打开注解扫描tx:annotation-driven/
l 全注解形式开启开关
@Configuration
@PropertySource({"classpath:db.properties"})
@ComponentScan("com.test.spring")
@EnableTransactionManagement
public class Spring{}