用spring连接数据库以及动态代理事务
1.1、数据源(连接池)的作用
提高程序性能,事先在连接池中创建连接,使用连接资源时从数据源中获取,使用完毕后将连接归还到
连接池。
常用的数据源:C3P0,DBCP,Druid等。
1.2、数据源的手动创建
1.2.1、使用步骤
- 导入相关Jar包
- 新建数据源对象
- 设置数据源的基本参数
- 使用数据源获取连接和归还资源
学了Spring IOC之后如何创建数据库连接
<!--
配置c3p0连接池
生成c3p0连接池的对象
class="com.mchange.v2.c3p0.ComboPooledDataSource"这一步相当于最原始的创建数据源
如下:
创建数据源...传统的方式使用new的方式创建对象
ComboPooledDataSource dataSource = new ComboPooledDataSource();
-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!--注入属性 需要set方法 万幸c3p0中有对应的set方法-->
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test"/>
<property name="user" value="root"/>
<property name="password" value="123456"/>
</bean>
测试文件
@Test
public void test1() throws SQLException {
//使用Spring容器创建c3p0连接池的对象
//解析配置文件---spring容器创建完成,Spring容器也具有了相应的bean对象
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
//获取c3p0
ComboPooledDataSource dataSource = (ComboPooledDataSource)context.getBean("dataSource");
System.out.println(dataSource);
//获取连接
Connection conn = dataSource.getConnection();
System.out.println(conn);
}
bean标签里面的数据库信息是写死的,如何解决
1.引用外部的配置文件
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
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">
<!--引用外部的jdbc的配置文件-->
<context:property-placeholder location="jdbc.properties"/>
<!--
配置c3p0连接池
生成c3p0连接池的对象
class="com.mchange.v2.c3p0.ComboPooledDataSource"这一步相当于最原始的创建数据源
如下:
创建数据源...传统的方式使用new的方式创建对象
ComboPooledDataSource dataSource = new ComboPooledDataSource();-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!--注入属性 需要set方法 万幸c3p0中有对应的set方法-->
<!--如何使用外部的配置文件中的配置-->
<property name="driverClass" value="${jdbc.drivername}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="user" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
</beans>
动态代理—作用:在不修改源码的情况下对方法进行增强
学习目的:为学习AOP做准备
实现方式
1.基于接口的动态代理,jdk官方提供
2.基于类的动态代理,第三方cglib库提供
几个概念:
目标对象:被增强的对象。Person p = new person();我给p添加新的东西,p就是目标对象
代理对象:被增强之后的对象 p p1 p1是被添加完增强的p对象,所以p1是代理对象
目标方法:被增强的方法 p里面有一个eat()方法,被增强后叫目标方法
代理对象 = 目标对象 + 增强
2.3.基于接口的动态代理
需要有接口
被代理类最少实现一个接口,如果没有则不能使用
2.3.1、创建目标类接口
public interface IWaiter {
void serve();
}
2.3.2、创建目标类
//目标类
public class ManWaiter implements IWaiter {
//目标方法
@Override
public void serve() {
System.out.println("服务...");
}
}
2.3.3、增强
//通知 --- 增强
public class Advice {
/*
前置增强,在目标方法之前运行
*/
public void before() {
System.out.println("您好...");
}
/*
后置增强,在目标方法之后运行
*/
public void after() {
System.out.println("再见...");
}
}
测试文件
//测试基于接口的动态代理,在不修改ManWaiter源码的情况下,对ManWaiter的方法进行增强
@Test
public void test1() {
//目标对象
IWaiter manWaiter = new ManWaiter();
//增强
Advice advice = new Advice();
//创建代理对象 代理对象 = 目标对象 + 增强
//代理对象
/**
* ClassLoader: 类加载器
* 用于加载代理对象字节码,和被代理对象使用相同的类加载器,写法固定
* Class<?>[]:字节码数组
* 用于让代理对象和被代理对象有相同方法,写法固定(指让目标对象和代理对象实现的是同一个接口)
* InvocationHandler:提供增强的代码
* 用于我们写如何代理,我们一般都行写这个接口的实现类,通常情况下是匿名内部类
*
*/
IWaiter waiter = (IWaiter) Proxy.newProxyInstance(manWaiter.getClass().getClassLoader(),
manWaiter.getClass().getInterfaces(), new InvocationHandler() {
@Override
/**
* 作用:执行被代理对象的任何接口方法都会经过该方法
* proxy:代理对象的引用
* method:当前执行的方法
* args:当前执行方法所需的参数
* 返回值:和被代理对象方法有相同的返回值
*
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//增强的内容
advice.before();
//执行目标对象的目标方法----manWaiter中的serve()
Object result = method.invoke(manWaiter, args);
//增强的内容
advice.after();
return result;
}
});
//执行目标对象的方法
waiter.serve();
}
2.4、cglib的动态代理
被代理类不需要实现接口
2.4.1、导入相关Jar包
2.4.2、创建目标类
public class WomanWaiter {
public void serve() {
System.out.println("服务....");
}
}
2.4.3、增强
和jdk动态代理的advice类一致
2.4.4、基于Cglib的动态代理
//基于cglib的动态代理
@Test
public void test2() {
//目标对象
WomanWaiter waiter = new WomanWaiter();
//增强
Advice advice = new Advice();
//创建代理对象 代理对象 = 目标对象 + 增强 代理对象可以认为是目标对象类型的子类
/**
* 涉及的类:Enhancer
* 提供者:第三方cglib库
* 如何创建代理对象:使用Enhancer类中的create方法
* 创建代理对象的要求:被代理类不能是最终类
* create方法的参数:
* Class:字节码
* 用于指定被代理对象的字节码
总结:创建代理对象的过程就是把“目标对象”和“增强”结合到⼀起的过程。
2.5、使用代理工厂实现动态代理
2.5.1、创建前置增强接口及其实现
* Callback:用于提供增强的代码
* 它是让我们写如何代理,一般都是写一些该接口的实现类,通常情况下是匿名内部类。
* 一般写的都是该接口的子接口实现类:MethodInterceptor
*
*/
WomanWaiter womanWaiter = (WomanWaiter) Enhancer.create(waiter.getClass(), new MethodInterceptor() {
@Override
/**
* 执行被代理对象的任何方法都会经过该方法
*
* 前三个参数和基于接口的动态代理中invoke方法的参数是一样的
*/
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
advice.before();
Object result = method.invoke(waiter, objects);
advice.after();
return result;
}
});
womanWaiter.serve();
}
2.5、使用代理工厂实现动态代理
三、使用动态代理实现事务
之前如何进行事务操作—工具类
----- 在service层处理事务 开启 提交 回滚
----- 采取方法对service层的方法进行增强
3.1、基础工程搭建
3.1.1、建库建表
DROP TABLE IF EXISTS `account`;
CREATE TABLE `account` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`money` int(11) NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Compact;
-- ----------------------------
-- Records of account
-- ----------------------------
INSERT INTO `account` VALUES (1, 'tom', 1000);
INSERT INTO `account` VALUES (2, 'bob', 1000);
SET FOREIGN_KEY_CHECKS = 1;
3.1.2、导入Jar包
3.1.3、创建account类
Account.java(数据库account表的实体类)
public class Account implements Serializable {
private Integer id;
private String name;
private Integer money;
//get和set方法
//tostring方法
}
3.1.4、创建AccountDao接口及其实现类
public interface IAccountDao {
List<Account> findAll() throws SQLException;
Account findById(Integer id) throws SQLException;
void save(Account account) throws SQLException;
void update(Account account) throws SQLException;
void delete(Integer id) throws SQLException;
}
public class AccountDaoImpl implements IAccountDao {
private QueryRunner queryRunner;
public void setQueryRunner(QueryRunner queryRunner) {
this.queryRunner = queryRunner;
}
@Override
public List<Account> findAll() throws SQLException {
Connection conn = JdbcUtils.getConnection();
String sql = "select * from account";
List<Account> list = queryRunner.query(conn, sql, new BeanListHandler<Account>(Account.class));
return list;
}
@Override
public Account findById(Integer id) throws SQLException {
Connection conn = JdbcUtils.getConnection();
String sql = "select * from account where id = ?";
Account account = queryRunner.query(conn, sql, new BeanHandler<Account>(Account.class), id);
return account;
}
@Override
public void save(Account account) throws SQLException {
Object[] params = {account.getName(), account.getMoney()};
Connection conn = JdbcUtils.getConnection();
String sql = "insert into account(name, money) values(?, ?)";
queryRunner.update(conn, sql, params);
}
@Override
public void update(Account account) throws SQLException {
Object[] params = {account.getMoney(), account.getId()};
Connection conn = JdbcUtils.getConnection();
String sql = "update account set money=? where id=?";
queryRunner.update(conn, sql, params);
}
@Override
public void delete(Integer id) throws SQLException {
Object[] params = {id};
Connection conn = JdbcUtils.getConnection();
String sql = "delete from account where id=?";
queryRunner.update(conn, sql, id);
}
}
3.1.5、创建AccountService接口及其实现类
public interface IAccountService {
List<Account> findAll() throws Exception;
Account findById(Integer id) throws Exception;
void save(Account account) throws Exception;
void update(Account account) throws Exception;
void delete(Integer id) throws Exception;
void transfer(Integer srcId, Integer dstId, Integer money) throws Exception;
}
public class AccountServiceImpl implements IAccountService {
private IAccountDao accountDao;
public void setAccountDao(IAccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
public List<Account> findAll() throws Exception {
return accountDao.findAll();
}
@Override
public Account findById(Integer id) throws Exception {
return accountDao.findById(id);
}
@Override
public void save(Account account) throws Exception {
accountDao.save(account);
}
@Override
public void update(Account account) throws Exception {
accountDao.update(account);
}
@Override
public void delete(Integer id) throws Exception {
accountDao.delete(id);
}
@Override
public void transfer(Integer srcId, Integer dstId, Integer money) throws Exception {
Account src = accountDao.findById(srcId);
Account dst = accountDao.findById(dstId);
if(src == null) {
throw new RuntimeException("转出用户不存在");
}
if(dst == null) {
throw new RuntimeException("转入用户不存在");
}
if(src.getMoney() < money) {
throw new RuntimeException("转出账户余额不足");
}
src.setMoney(src.getMoney() - money);
dst.setMoney(dst.getMoney() + money);
accountDao.update(src);
//int x = 1/0;
accountDao.update(dst);
}
}
3.1.6、创建处理事务的工具类
3.2、基于动态代理实现事务
3.2.1、创建增强接口及其实现类
Before是开启事务
After是提交事务
Act是回滚事务
3.2.2、创建代理工厂
public class ProxyFactory {
private Object targetObject;
private BeforeAdvice beforeAdvice;
private AfterAdvice afterAdvice;
private ActAdvice actAdvice;
public ActAdvice getActAdvice() {
return actAdvice;
}
public void setActAdvice(ActAdvice actAdvice) {
this.actAdvice = actAdvice;
}
public Object getTargetObject() {
return targetObject;
}
public void setTargetObject(Object targetObject) {
this.targetObject = targetObject;
}
public BeforeAdvice getBeforeAdvice() {
return beforeAdvice;
}
public void setBeforeAdvice(BeforeAdvice beforeAdvice) {
this.beforeAdvice = beforeAdvice;
}
public AfterAdvice getAfterAdvice() {
return afterAdvice;
}
public void setAfterAdvice(AfterAdvice afterAdvice) {
this.afterAdvice = afterAdvice;
}
public Object createProxyObject() {
ClassLoader classLoader = this.targetObject.getClass().getClassLoader();
Class[] interfaces = this.targetObject.getClass().getInterfaces();
InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object resultValue = null;
try {
if(beforeAdvice != null) {
beforeAdvice.before();
}
resultValue = method.invoke(targetObject, args);
if(afterAdvice != null) {
afterAdvice.after();
}
} catch (Exception e) {
e.printStackTrace();
if(actAdvice != null) {
actAdvice.act();
}
}
return resultValue;
}
};
return Proxy.newProxyInstance(classLoader, interfaces, handler);
}
}
3.2.3、编写JDBC配置文件
public class JdbcUtils {
//正常应该new这里用spring配置
private static DataSource dataSource;
3.2.4、在Spring配置文件中进行配置
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
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">
<!-- 引入外部配置文件 -->
<context:property-placeholder location="classpath:jdbc.properties" />
<!-- 配置bean -->
<!-- 创建数据源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!-- 注入相关参数 -->
<property name="driverClass" value="${jdbc.driver}" />
<property name="jdbcUrl" value="${jdbc.url}" />
<property name="user" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</bean>
<bean class="com.hpe.util.JdbcUtils">
<!--为工具类注入数据源com.hpe.util.JdbcUtils下的dataSource-->
<property name="dataSource" ref="dataSource"/>
</bean>
<!--创建queryrunner-->
<bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner"/>
<!--引用queryrunner-->
<bean id="accountDao" class="com.hpe.dao.impl.AccountDaoImpl">
<property name="queryRunner" ref="queryRunner"/>
</bean>
<bean id="accountService" class="com.hpe.service.impl.AccountServiceImpl">
<!--注入dao,以前在service里面new的步骤-->
<property name="accountDao" ref="accountDao"/>
</bean>
<!--和增强相关的对象-->
<!--前置增强-->
<bean id="beforeAdvice" class="com.hpe.proxy.advice.impl.BeforeAdviceImpl"/>
<!--后置增强-->
<bean id="afterAdvice" class="com.hpe.proxy.advice.impl.AfterAdviceImpl"/>
<!--回滚增强-->
<bean id="actAdvice" class="com.hpe.proxy.advice.impl.ActAdviceImpl"/>
<!--代理工厂 只能生成有默认构造器的对象-->
<bean id="proxyFactory" class="com.hpe.proxy.ProxyFactory">
<!--希望被增强的对象,accountservice-->
<property name="targetObject" ref="accountService"/>
<property name="beforeAdvice" ref="beforeAdvice"/>
<property name="afterAdvice" ref="afterAdvice"/>
<property name="actAdvice" ref="actAdvice"/>
</bean>
<!--代理对象-->
<!-- 通过代理工厂,创建Service对象, 这也是配置bean的一种方式,
但是不常用,在Spring第一次课讲过 -->
<bean id="proxyaccountservice" factory-bean="proxyFactory" factory-method="createProxyObject"/>
</beans>
3.2.5、测试
public class MyTest {
@Test
public void test1() throws Exception {
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
//本质上是对IAccountService进行增强
IAccountService proxyaccountservice = (IAccountService) context.getBean("proxyaccountservice");
proxyaccountservice.transfer(1, 2, 10);
}
}
运行测试方法,程序正常运行,能够正常转账。
将3.1.5中,AccountServiceImpl两次更新操作中间加⼊int i = 100/0; 再运行测试方法,程序能够回滚。