1.Spring注解深入
在Spring框架基础01中,我们使用了xml配置文件声明扫描包下的注解,那么能否不用xml声明扫描注解,而用注解的方式声明扫描注解呢?答案是可以的
Configuration注解
作用:指定当前类是一个配置类
细节:当配置类作为AnnotationConfigApplicationContext对象创建的参数时,该注解可以不写。
Config配置类
/**
* 这个类是个配置类,用来代替bean.xml
* 需要使用注解@Configuration声明
*/
@Configuration
public class ConfigXml {
}
ComponentScan注解
作用:用于通过注解指定spring在创建容器时要扫描的包
value:它和basePackages的作用是一样的,都是用于指定创建容器时要扫描的包。
我们使用此注解就等同于在xml中配置了:
<context:component-scan base-package="it.rose"></context:component-scan>
Config配置类
/**
* 这个类是个配置类,用来代替bean.xml
* 需要使用注解@Configuration声明
*/
@Configuration
@ComponentScan("it.rose")//告诉这个配置类,要扫描的包
public class ConfigXml {
}
Bean注解
作用:用于把当前方法的返回值作为bean对象存入spring的ioc容器中
属性:name:用于指定bean的id。当不写时,默认值是当前方法的名称
细节: 当我们使用注解配置方法时,如果方法有参数,spring框架会去容器中查找有没有可用的bean对象。
查找的方式和Autowired注解的作用是一样的
Config配置类
/**
* 这个类是个配置类,用来代替bean.xml
* 需要使用注解@Configuration声明
*/
@Configuration
@ComponentScan("it.code")//告诉这个配置类,要扫描的包
public class ConfigXml {
@Bean //bean对象名称:createQueryRunner
public QueryRunner createQueryRunner(DataSource dataSource) {
return new QueryRunner(dataSource);
}
@Bean //bean对象名称:createDataSource
public DataSource createDataSource(){
try {
ComboPooledDataSource ds = new ComboPooledDataSource();
ds.setDriverClass("com.mysql.jdbc.Driver");
ds.setJdbcUrl("jdbc:mysql://localhost:3306/test?true&characterEncoding=utf8");
ds.setUser("root");
ds.setPassword("root");
return ds;
}catch (Exception e){
throw new RuntimeException(e);
}
}
}
Import注解
作用:用于导入其他的配置类
属性:value:用于指定其他配置类的字节码。
当我们使用Import的注解之后,有Import注解的类就父配置类,而导入的都是子配置类
Config配置类
ConfigXml
@Import(ConfigXml_1.class)
public class ConfigXml {
}
ConfigXml_1
@Configuration
@ComponentScan("it.code")
public class ConfigXml_1 {
@Bean //bean对象名称:createQueryRunner
public QueryRunner createQueryRunner(DataSource dataSource) {
return new QueryRunner(dataSource);
}
@Bean //bean对象名称:createDataSource
public DataSource createDataSource(){
try {
ComboPooledDataSource ds = new ComboPooledDataSource();
ds.setDriverClass("com.mysql.jdbc.Driver");
ds.setJdbcUrl("jdbc:mysql://localhost:3306/test?true&characterEncoding=utf8");
ds.setUser("root");
ds.setPassword("root");
return ds;
}catch (Exception e){
throw new RuntimeException(e);
}
}
}
PropertySource注解
作用:用于加载.properties 文件中的配置。例如我们配置数据源时,可以把连接数据库的信息写到properties 配置文件中,就可以使用此注解指定 properties 配置文件的位置。
属性: value:指定文件的名称和路径。
关键字:classpath,表示类路径下
Config配置类
@Configuration
@ComponentScan("it.code")
@PropertySource("classpath:jdbcConfig.properties")
public class JdbcConfig {
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
/**
* 创建一个数据源,并存入 spring 容器中
* @return
*/
@Bean(name="dataSource")
public DataSource createDataSource() {
try {
ComboPooledDataSource ds = new ComboPooledDataSource();
ds.setDriverClass(driver);
ds.setJdbcUrl(url);
ds.setUser(username);
ds.setPassword(password);
return ds;
} catch (Exception e) {
throw new RuntimeException(e);
} }
@Bean //bean对象名称:createQueryRunner
public QueryRunner createQueryRunner(DataSource dataSource) {
return new QueryRunner(dataSource);
}
}
代码分析
properties文件
Qualifier注解
在Spring框架基础01中,Qualifier注解必须和Autowired注解一起使用,给类注入数据。
其实单独使用Qualifier注解还可以给方法注入数据
2. Spring整合Junit
在测试类中,每个测试方法都有以下两行代码:
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
IAccountService as = ac.getBean("accountService",IAccountService.class);
这两行代码的作用是获取容器,如果不写的话,直接会提示空指针异常。所以又不能轻易删掉。
1.导入spring整合junit的jar(坐标)
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
注意:要告知Spring需要注入的对象类型,也既前面的@Autowired 注解
@Autowired //告知Spring程序开始时需要注入对象的类型
private IAccountsDaoImpl ia;
2.使用@Runwith注解
使用Junit提供的一个注解(@Runwith注解)把原有的测试方法替换了,替换成spring提供的测试
@RunWith(SpringJUnit4ClassRunner.class)
3.告知spring基于xml还是注解,并说明位置
@ContextConfiguration(locations="classpath:bean.xml")
4.实现原理
3. Spring中的AOP
1. 什么是AOP
简单的说它就是把我们程序重复的代码抽取出来,在需要执行的时候,使用动态代理的技术,在不修改源码的基础上,对我们的已有方法进行增强。就是动态代理
利用动态代理实现,在执行账户的业务层之前,添加一行日志
账户的业务层接口
/**
* 账户的业务层接口
*/
public interface IAccountService {
/**
* 模拟保存账户
*/
void saveAccount();
/**
* 模拟更新账户
* @param i
*/
void updateAccount(int i);
/**
* 删除账户
* @return
*/
int deleteAccount();
}
账户的业务层实现类
/**
* 账户的业务层实现类
*/
public class AccountServiceImpl implements IAccountService {
@Override
public void saveAccount() {
System.out.println("执行了保存");
}
@Override
public void updateAccount(int i) {
System.out.println("执行了更新"+i);
}
@Override
public int deleteAccount() {
System.out.println("执行了删除");
return 0;
}
}
怎么使账户的业务层实现类的方法增强呢?
使用动态代理
public class test01 {
public static void main(String[] args) {
ApplicationContext alc=new ClassPathXmlApplicationContext("bean.xml");
Logger logger = alc.getBean("logger",Logger.class);
AccountServiceImpl service = alc.getBean("serivce",AccountServiceImpl.class);
IAccountService proxy =(IAccountService) Proxy.newProxyInstance(service.getClass().getClassLoader(),
service.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
logger.printLog();
Object returns = method.invoke(service, args);
return returns;
}
});
proxy.updateAccount(20);
附注:日志代码
/**
* 用于记录日志的工具类,它里面提供了公共的代码
*/
public class Logger {
/**
* 用于打印日志:计划让其在切入点方法执行之前执行(切入点方法就是业务层方法)
*/
public void printLog(){
System.out.println("Logger类中的pringLog方法开始记录日志了。。。");
}
}
实现原理
其实上面的方式就是AOP实现原理,在不修改源码的基础上,对我们的已有方法进行增强。
而在Spring中,就是使用AOP配置来配置上面过程
实现AOP的依赖
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.2</version>
</dependency>
要和jdk版本向适应,否则报错
2.AOP相关术语(了解)
Joinpoint(连接点):所谓连接点是指那些被拦截到的点。在spring中,这些点指的是方法,因为 spring 只支持方法类型的连接点。
大白话:连接点就是连接业务和增强方法的点(方法),既接口中的方法(接口下面两兄弟:被代理对象,代理对象,接口的方法对其连接)
Pointcut(切入点):所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义。
大白话:就是被代理对象中要增强的方法
连接点不一定是切入点,切入点一定是连接点
也既:连接的方法不一定被增强,增强的方法一定被连接
Advice(通知/增强):所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知。
通知的类型:前置通知,后置通知,异常通知,最终通知,环绕通知。
Introduction(引介):引介是一种特殊的通知在不修改类代码的前提下, Introduction 可以在运行期为类动态地添加一些方法或 Field。
Target(目标对象):代理的目标对象。
Weaving(织入):是指把增强应用到目标对象来创建新的代理对象的过程。spring 采用动态代理织入,而 AspectJ 采用编译期织入和类装载期织入。
Proxy(代理):一个类被 AOP 织入增强后,就产生一个结果代理类。
Aspect(切面):是切入点和通知(引介)的结合。
3.Spring中的AOP实现01
账户的业务层接口
/**
* 账户的业务层接口
*/
public interface IAccountService {
/**
* 模拟保存账户
*/
void saveAccount();
/**
* 模拟更新账户
* @param i
*/
void updateAccount(int i);
/**
* 删除账户
* @return
*/
int deleteAccount();
}
账户的业务层实现类
/**
* 账户的业务层实现类
*/
public class AccountServiceImpl implements IAccountService {
@Override
public void saveAccount() {
System.out.println("执行了保存");
}
@Override
public void updateAccount(int i) {
System.out.println("执行了更新"+i);
}
@Override
public int deleteAccount() {
System.out.println("执行了删除");
return 0;
}
}
怎么使账户的业务层实现类的方法增强呢?
使用AOP配置
1、使用aop:config标签表明开始AOP的配置
<aop:config></aop:config>
2、使用aop:aspect标签表明配置切面:
- id属性:是给切面提供一个唯一标识,
- ref属性:是指定通知类bean的Id。
<aop:config>
<aop:aspect id="logAdvice" ref="logger"> </aop:aspect>
</aop:config>
3、在aop:aspect标签的内部使用对应标签来配置通知的类型
我们现在示例是让printLog方法在切入点方法执行之前之前:所以是前置通知
- aop:before:表示配置前置通知
-
- method属性:用于指定Logger类中哪个方法是前置通知
-
- pointcut属性:用于指定切入点表达式,该表达式的含义指的是对业务层中哪些方法增强
3、pointcut属性中的切入点表达式的写法
- 关键字:execution(表达式)
- 表达式: 访问修饰符 返回值 包名.包名.包名…类名.方法名(参数列表)
例如上面:pointcut="execution(public void it.cast.service.impl.AccountServiceImpl.saveAccount())"
<!-- 配置Logger类 -->
<bean id="logger" class="it.cast.utils.Logger"></bean>
<bean id="serivce" class="it.cast.service.impl.AccountServiceImpl"></bean>
<!-- aop的配置 -->
<aop:config>
<aop:aspect id="logAdvice" ref="logger">
<aop:before method="printLog" pointcut="execution(public void it.cast.service.impl.AccountServiceImpl.saveAccount())"></aop:before>
</aop:aspect>
</aop:config>
配置分析
附注:
切入点表达式的写法:
关键字:execution(表达式)
表达式:
访问修饰符 返回值 包名.包名.包名...类名.方法名(参数列表)
标准的表达式写法:
public void com.itheima.service.impl.AccountServiceImpl.saveAccount()
访问修饰符可以省略
void com.itheima.service.impl.AccountServiceImpl.saveAccount()
返回值可以使用通配符,表示任意返回值
* com.itheima.service.impl.AccountServiceImpl.saveAccount()
包名可以使用通配符,表示任意包。但是有几级包,就需要写几个*.
* *.*.*.*.AccountServiceImpl.saveAccount())
包名可以使用..表示当前包及其子包
* *..AccountServiceImpl.saveAccount()
类名和方法名都可以使用*来实现通配
* *..*.*()
参数列表:
可以直接写数据类型:
基本类型直接写名称 int
引用类型写包名.类名的方式 java.lang.String
可以使用通配符表示任意类型,但是必须有参数
可以使用..表示有无参数均可,有参数可以是任意类型
全通配写法:
* *..*.*(..)
实际开发中切入点表达式的通常写法:
切到业务层实现类下的所有方法
* com.itheima.service.impl.*.*(..)
测试代码
public class test02 {
public static void main(String[] args) {
ApplicationContext alc=new ClassPathXmlApplicationContext("bean01.xml");
String[] names = alc.getBeanDefinitionNames();
IAccountService serivce = alc.getBean("serivce", IAccountService.class);
serivce.saveAccount();
}
}
注意:这里返回的数据类型一定为接口
4.Spring中的AOP实现02
<!-- 配置Logger类 -->
<bean id="logger" class="it.code.utils.Logger"></bean>
<bean id="serivce" class="it.code.service.impl.AccountServiceImpl"></bean>
<!-- aop的配置 -->
<aop:config>
<aop:pointcut id="pt1" expression="execution(public void it.code.service.impl.AccountServiceImpl.*(..)))"></aop:pointcut>
<aop:aspect id="logAdvice" ref="logger">
<!-- 配置前置通知:在切入点方法执行之前执行-->
<aop:before method="printLogBefore" pointcut-ref="pt1" ></aop:before>
<!-- 配置后置通知:在切入点方法正常执行之后值。它和异常通知永远只能执行一个-->
<aop:after-returning method="printLogAfter" pointcut-ref="pt1"></aop:after-returning>
<!-- 配置异常通知:在切入点方法执行产生异常之后执行。它和后置通知永远只能执行一个-->
<aop:after-throwing method="printLogthrow" pointcut-ref="pt1"></aop:after-throwing>
<!-- 配置最终通知:无论切入点方法是否正常执行它都会在其后面执行-->
<aop:after method="printLogFinal" pointcut-ref="pt1"></aop:after>
</aop:aspect>
</aop:config>
aop:pointcut:标签就是给被切入的方法取别名
- 配置切入点表达式 id属性用于指定表达式的唯一标识。expression属性用于指定表达式内容
- 此标签写在aop:aspect标签内部只能当前切面使用。
- 它还可以写在aop:aspect外面,此时就变成了所有切面可用
注意:放在aop:aspect标签前面
为什么没有异常通知,因为,程序本身没有异常,不会跑到catch中去。
配置环绕通知
Spring框架为我们提供了一个接口:ProceedingJoinPoint
。该接口有一个方法proceed()
,此方法就相当于明确调用切入点方法。
xml配置
<aop:around method="aroundPringLog" pointcut-ref="pt1"></aop:around>
在要切入的方法中,
/**
* 环绕通知
* 问题:
* 当我们配置了环绕通知之后,切入点方法没有执行,而通知方法执行了。
* 分析:
* 通过对比动态代理中的环绕通知代码,发现动态代理的环绕通知有明确的切入点方法调用,而我们的代码中没有。
* 解决:
* Spring框架为我们提供了一个接口:ProceedingJoinPoint。该接口有一个方法proceed(),此方法就相当于明确调用切入点方法。
* 该接口可以作为环绕通知的方法参数,在程序执行时,spring框架会为我们提供该接口的实现类供我们使用。
*
* spring中的环绕通知:
* 它是spring框架为我们提供的一种可以在代码中手动控制增强方法何时执行的方式。
*/
public Object aroundPringLog(ProceedingJoinPoint pjp){
Object rtValue = null;
try{
Object[] args = pjp.getArgs();//得到方法执行所需的参数
System.out.println("前置通知");
rtValue = pjp.proceed(args);//明确调用业务层方法(切入点方法)
System.out.println("后置通知");
return rtValue;
}catch (Throwable t){
System.out.println("异常通知");
throw new RuntimeException(t);
}finally {
System.out.println("最终通知");
}
}
测试结果
5.注解实现AOP
1、首先重新导入注解的xml约束
2、bean.xml标签中,声明
bean.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 配置spring创建容器时要扫描的包-->
<context:component-scan base-package="com.itheima"></context:component-scan>
<!-- 配置spring开启注解AOP的支持 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
3、切入声明
/**
* 用于记录日志的工具类,它里面提供了公共的代码
*/
@Component
@Aspect//表示当前类是一个切面类
public class Logger {
@Pointcut("execution(public void it.rose.service.impl.AccountServiceImpl.*(..))))")
private void pt1(){}
/**
* 前置通知
*/
@Before("pt1()")
public void printLogBefore(){
System.out.println("配置前置通知");
}
/**
* 后置通知
*/
@AfterReturning("pt1()")
public void printLogAfter(){
System.out.println("配置后置通知");
}
/**
* 异常通知
*/
@AfterThrowing("pt1()")
public void printLogthrow(){
System.out.println("配置前置通知");
}
/**
* 最终通知
*/
@After("pt1()")
public void printLogFinal(){
System.out.println("配置后置通知");
}
}
Aspect 注解:表示当前类是一个切面类(插入类)
Pointcut注解:指定切入点表达式
Before 注解:前置通知
AfterReturning 注解:后置通知
AfterThrowing 注解:异常通知
After 注解:最终通知
测试结果
为什么先是最终通知,然后才是后置通知呢?
这是Spring的bug,所以实际开发中,一般不用这方式
环绕通知
@Component
@Aspect//表示当前类是一个切面类
public class Logger {
@Pointcut("execution(public void it.rose.service.impl.AccountServiceImpl.*(..))))")
private void pt1(){}
@Around("pt1()")
public Object aroundPringLog(ProceedingJoinPoint pjp){
Object rtValue = null;
try{
Object[] args = pjp.getArgs();//得到方法执行所需的参数
System.out.println("前置通知");
rtValue = pjp.proceed(args);//明确调用业务层方法(切入点方法)
System.out.println("后置通知");
return rtValue;
}catch (Throwable t){
System.out.println("异常通知");
throw new RuntimeException(t);
}finally {
System.out.println("最终通知");
}
}
}
测试结果
虽然单个通知可能出现bug,但是环绕通知不会出现这种情况
4. Spring中的JdbcTemplate对象
它是 spring 框架中提供的一个对象,是对原始 Jdbc API 对象的简单封装。spring 框架为我们提供了很多的操作模板类。
需要导入的依赖
<dependencies>
<!-- spring核心 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<!-- Spring的jdbc-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<!-- spring的事务 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<!--数据库连接-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
</dependencies>
1.JdbcTemplate构造方法
public JdbcTemplate() {
}
public JdbcTemplate(DataSource dataSource) {
setDataSource(dataSource);
afterPropertiesSet();
}
public JdbcTemplate(DataSource dataSource, boolean lazyInit) {
setDataSource(dataSource);
setLazyInit(lazyInit);
afterPropertiesSet();
}
除了默认构造函数之外,都需要提供一个数据源。
即使是默认构造,也需要setDataSource
添加数据源
使用complate添加数据
public class testJdbcTemplate {
public static void main(String[] args) {
// 准备数据源:spring的内置数据源
DriverManagerDataSource ds = new DriverManagerDataSource();
JdbcTemplate template = new JdbcTemplate();
ds.setDriverClassName("com.mysql.jdbc.Driver");
ds.setUrl("jdbc:mysql://localhost:3306/test");
ds.setUsername("root");
ds.setPassword("root");
template.setDataSource(ds);
template.execute("insert into accounts(name,money)values('ccc',1000)");
}
}
添加结果
2.JdbcTemplate实现CRUD
完整代码
public class testJdbcTemplate02 {
public static void main(String[] args) {
ClassPathXmlApplicationContext alc = new ClassPathXmlApplicationContext("bean.xml");
JdbcTemplate template = alc.getBean("template", JdbcTemplate.class);
//添加操作
template.execute("insert into accounts(name,money)values('ccc',1000)");
//更新操作
template.update("update accounts set money = money-? where id = ?",300,6);
//删除操作
template.update("delete from accounts where id = ?",6);
//查询所有操作
List<Object> list1 = template.query("select * from accounts", new RowMapper<Object>() {
@Override
public Object mapRow(ResultSet rs, int i) throws SQLException {
Account account = new Account();
account.setId(rs.getInt("id"));
account.setName(rs.getString("name"));
account.setMoney(rs.getFloat("money"));
return account;
}
});
for (Object as: list1){
Account account=(Account) as;
System.out.println(account);
}
//查询一个操作
List<Object> obj = template.query("select * from accounts where id = ? ", new RowMapper<Object>() {
@Override
public Object mapRow(ResultSet rs, int i) throws SQLException {
Account account = new Account();
account.setId(rs.getInt("id"));
account.setName(rs.getString("name"));
account.setMoney(rs.getFloat("money"));
return account;
}
}, 7);
for (Object as: obj){
Account account=(Account) as;
System.out.println(account);
}
//查询查询返回一行一列操作
Integer total = template.queryForObject("select count(*) from accounts where money > ? ",Integer.class,500);
System.out.println(total);
}
}
这里需要特别说明一下query()方法,该方法需要传递一个RowMapper接口,该接口既可以我们自己实现,也可以使用已经实现的BeanPropertyRowMapper实现类。
选择已经实现好的类去实现
public class testJdbcTemplate03 {
public static void main(String[] args) {
ClassPathXmlApplicationContext alc = new ClassPathXmlApplicationContext("bean.xml");
JdbcTemplate template = alc.getBean("template", JdbcTemplate.class);
List<Account> query = template.query("select * from accounts where id = ? ", new BeanPropertyRowMapper<>(Account.class), 3);
for (Account account:query){
System.out.println(query);
}
}
}
直接调用BeanPropertyRowMapper方法,需要传递泛型,以及泛型的字节码文件;
3.dao中使用JdbcTemplate(实际开发)
实现原理
为什么要搞一个公共父类呢?
因为:在实际开发中会有许多持久层接口,那么必然会有许多实现持久层接口的类,如果没有类都去下面这样,那么代码将重复,并且IOC容器配置也会有许多,所有Spring也提供了一个这样的类JdbcDaoSupport,简化开发
private JdbcTemplate jdbcTemplate;
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
实现步骤:
持久层接口
public interface IAccount {
/**
* 根据Id查询账户
* @param accountId
* @return
*/
Account findAccountById(Integer accountId);
/**
* 根据名称查询账户
* @param accountName
* @return
*/
Account findAccountByName(String accountName);
/**
* 更新账户
* @param account
*/
void updateAccount(Account account);
}
持久层接口实现
这里使用的是Spring提供的JdbcDaoSupport作为继承
/**
* 账户的持久层实现
*/
public class IAccountImpl extends JdbcDaoSupport implements IAccount {
@Override
public Account findAccountById(Integer accountId) {
List<Account> accounts = super.getJdbcTemplate().query("select * from account where id = ?",new BeanPropertyRowMapper<Account>(Account.class),accountId);
return accounts.isEmpty()?null:accounts.get(0);
}
@Override
public Account findAccountByName(String accountName) {
List<Account> accounts = super.getJdbcTemplate().query("select * from account where name = ?",new BeanPropertyRowMapper<Account>(Account.class),accountName);
if(accounts.isEmpty()){
return null;
}
if(accounts.size()>1){
throw new RuntimeException("结果集不唯一");
}
return accounts.get(0);
}
@Override
public void updateAccount(Account account) {
super.getJdbcTemplate().update("update account set name=?,money=? where id=?",account.getName(),account.getMoney(),account.getId());
}
}
如果不是用继承,而是定义 private JdbcTemplate jdbcTemplate;
如下
/**
* 账户的持久层实现
*/
public class IAccountImpl1 implements IAccount {
private JdbcTemplate jdbcTemplate;
@Override
public Account findAccountById(Integer accountId) {
List<Account> accounts = jdbcTemplate.query("select * from account where id = ?",new BeanPropertyRowMapper<Account>(Account.class),accountId);
return accounts.isEmpty()?null:accounts.get(0);
}
@Override
public Account findAccountByName(String accountName) {
List<Account> accounts = jdbcTemplate.query("select * from account where name = ?",new BeanPropertyRowMapper<Account>(Account.class),accountName);
if(accounts.isEmpty()){
return null;
}
if(accounts.size()>1){
throw new RuntimeException("结果集不唯一");
}
return accounts.get(0);
}
@Override
public void updateAccount(Account account) {
jdbcTemplate.update("update account set name=?,money=? where id=?",account.getName(),account.getMoney(),account.getId());
}
}
缺点:代码重复,而且需要配置许多bean标签
测试实现
public class testJdbcTemplate04 {
public static void main(String[] args) {
ClassPathXmlApplicationContext alc = new ClassPathXmlApplicationContext("bean.xml");
IAccountImpl accounts = alc.getBean("accounts", IAccountImpl.class);
Account accountById = accounts.findAccountById(3);
System.out.println(accountById);
}
}
当然我们可以自定义一个JdbcDaoSupport类似公能的(也很简单)
public class JdbcDaoSupport {
private JdbcTemplate jdbcTemplate;
public JdbcDaoSupport(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
}
5. Spring中的事务控制
在前面aop编程中,我们都是自己写的事务控制(“模仿”),其实在Spring中,已经为我们提供好了的事务控制管理的一个接口PlatformTransactionManager
此接口是 spring 的事务管理器,它里面提供了我们常用的操作事务的方法
我们在开发中都是使用它的实现类DataSourceTransactionManager
或者HibernateTransactionManager
1.基于XML的声明式事务控制
持久层实现(持久层接口省略)
/**
* 账户的持久层实现类
*/
public class AccountDaoImpl extends JdbcDaoSupport implements IAccountDao {
@Override
public Account findAccountById(Integer accountId) {
List<Account> accounts = super.getJdbcTemplate().query("select * from accounts where id = ?",new BeanPropertyRowMapper<Account>(Account.class),accountId);
return accounts.isEmpty()?null:accounts.get(0);
}
@Override
public Account findAccountByName(String accountName) {
List<Account> accounts = super.getJdbcTemplate().query("select * from accounts where name = ?",new BeanPropertyRowMapper<Account>(Account.class),accountName);
if(accounts.isEmpty()){
return null;
}
if(accounts.size()>1){
throw new RuntimeException("结果集不唯一");
}
return accounts.get(0);
}
@Override
public void updateAccount(Account account) {
super.getJdbcTemplate().update("update accounts set name=?,money=? where id=?",account.getName(),account.getMoney(),account.getId());
}
}
业务层实现(业务层接口省略)
/**
* 账户的业务层实现类
*
* 事务控制应该都是在业务层
*/
public class AccountServiceImpl implements IAccountService{
private IAccountDao accountDao;
public void setAccountDao(IAccountDao accountDao) {
this.accountDao = accountDao;
}
/**
* 根据id查询账户信息
* @param accountId
* @return
*/
@Override
public Account findAccountById(Integer accountId) {
return accountDao.findAccountById(accountId);
}
/**
* 转账
* @param sourceName 转成账户名称
* @param targetName 转入账户名称
* @param money 转账金额
*/
@Override
public void transfer(String sourceName, String targetName, Float money) {
System.out.println("transfer....");
//2.1根据名称查询转出账户
Account source = accountDao.findAccountByName(sourceName);
//2.2根据名称查询转入账户
Account target = accountDao.findAccountByName(targetName);
//2.3转出账户减钱
source.setMoney(source.getMoney()-money);
//2.4转入账户加钱
target.setMoney(target.getMoney()+money);
//2.5更新转出账户
accountDao.updateAccount(source);
//2.6更新转入账户
accountDao.updateAccount(target);
}
}
bean.xml配置
<bean id="AccountService" class="com.cost.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"></property>
</bean>
<!--由于继承了JdbcDaoSupport所以需要数据源-->
<bean id="accountDao" class="com.cost.dao.impl.AccountDaoImpl">
<property name="dataSource" ref="ds"></property>
</bean>
<!--注入数据源-->
<bean id="ds" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="url" value="jdbc:mysql://localhost:3306/test"></property>
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="password" value="root"></property>
<property name="username" value="root"></property>
</bean>
测试代码
public class AccountServiceTest {
public static void main(String[] args) {
ClassPathXmlApplicationContext plc = new ClassPathXmlApplicationContext("bean.xml");
IAccountService service =plc.getBean("AccountService", IAccountService.class);
service.transfer("aaa","bbb",100f);
}
}
当我们业务层转账方法没有异常时,可以转账成功
当我们业务层有异常时,就会导致如下结果
业务层添加异常
出现结果
原因:因为我们没有进行事务控制导致的,前面在AOP中,我们手动的去添加事务
解决办法:添加AOP为我们提供的事务管理
1.配置事务管理器(Spring为我们提供的)
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 注入 DataSource -->
<property name="dataSource" ref="ds"></property>
</bean>
需要提供数据源
2.配置事务的通知
<tx:advice id="txAdvice" transaction-manager="transactionManager"> </tx:advice>
3.配置事务的属性(在tx:advice标签内部)
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!-- 配置事务的属性
isolation:用于指定事务的隔离级别。默认值是DEFAULT,表示使用数据库的默认隔离级别。
propagation:用于指定事务的传播行为。默认值是REQUIRED,表示一定会有事务,增删改的选择。查询方法可以选择SUPPORTS。
read-only:用于指定事务是否只读。只有查询方法才能设置为true。默认值是false,表示读写。
timeout:用于指定事务的超时时间,默认值是-1,表示永不超时。如果指定了数值,以秒为单位。
rollback-for:用于指定一个异常,当产生该异常时,事务回滚,产生其他异常时,事务不回滚。没有默认值。表示任何异常都回滚。
no-rollback-for:用于指定一个异常,当产生该异常时,事务不回滚,产生其他异常时事务回滚。没有默认值。表示任何异常都回滚。
-->
<tx:attributes>
<tx:method name="*" propagation="REQUIRED" read-only="false"/>
<tx:method name="find*" propagation="SUPPORTS" read-only="true"></tx:method>
</tx:attributes>
</tx:advice>
配置aop切入点表达式
<!-- 配置aop-->
<aop:config>
<!-- 配置切入点表达式-->
<aop:pointcut id="pt1" expression="execution(* com.cost.service.impl.*.*(..))"></aop:pointcut>
<!--建立切入点表达式和事务通知的对应关系 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"></aop:advisor>
</aop:config>
为什么在配置aop切入点表达式中没有开启事务,提交事务,回滚事务,和释放连接等操作呢?
因为:Spring知道
2.基于注解的配置事务
1.配置bean.xml标签使事务支持注解
1.基础配置
<!-- 配置 spring 创建容器时要扫描的包 -->
<context:component-scan base-package="com.rose"></context:component-scan>
<!--这里由于AccountDaoImpl使用的是extends JdbcDaoSupport,所以只能通过xml配置方式注入数据jdbcTemplate-->
<bean id="accountDao" class="com.rose.dao.impl.AccountDaoImpl">
<property name="jdbcTemplate" ref="jdbcTemplate"></property>
</bean>
<!-- 配置 JdbcTemplate-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="ds"></property>
</bean>
<!--注入数据源-->
<bean id="ds" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="url" value="jdbc:mysql://localhost:3306/test"></property>
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="password" value="root"></property>
<property name="username" value="root"></property>
</bean>
2.事务管理配置
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="ds"></property>
</bean>
<!-- 开启spring对注解事务的支持-->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
注意:要加入xmlns:context="http://www.springframework.org/schema/context"等约束
2.配置注解
/**
* 账户的持久层实现类
*/
@Repository("accountDao")
public class AccountDaoImpl extends JdbcDaoSupport implements IAccountDao
/**
* 账户的业务层实现类
*
* 事务控制应该都是在业务层
*/
@Service("accountService")
public class AccountServiceImpl implements IAccountService
3.配置事务管理注解
在账户的业务层实现类
@Service("accountService")
@Transactional()
public class AccountServiceImpl implements IAccountService {
拓展:
当然Transactional中有许多属性,如下
@Transactional(readOnly=true,propagation=Propagation.SUPPORTS)
但是如果在类中加入上面一句,会出现如下问题
不使用xml的配置方式
/**
* 和连接数据库相关的配置类,需要一个jdbc.properties配置文件
*/
public class JdbcConfig {
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
/**
* 创建JdbcTemplate
* @param dataSource
* @return
*/
@Bean(name="jdbcTemplate")
public JdbcTemplate createJdbcTemplate(DataSource dataSource){
return new JdbcTemplate(dataSource);
}
/**
* 创建数据源对象
* @return
*/
@Bean(name="dataSource")
public DataSource createDataSource(){
DriverManagerDataSource ds = new DriverManagerDataSource();
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(username);
ds.setPassword(password);
return ds;
}
}
/**
* 和事务相关的配置类
*/
public class TransactionConfig {
/**
* 用于创建事务管理器对象
* @param dataSource
* @return
*/
@Bean(name="transactionManager")
public PlatformTransactionManager createTransactionManager(DataSource dataSource){
return new DataSourceTransactionManager(dataSource);
}
}
/**
* spring的配置类,相当于bean.xml
*/
@Configuration
@ComponentScan("com.rost")
@Import({JdbcConfig.class,TransactionConfig.class})
@PropertySource("jdbcConfig.properties")
@EnableTransactionManagement
public class SpringConfiguration {
}