用spring连接数据库以及动态代理事务

  • 1.1、数据源(连接池)的作用
  • 1.2、数据源的手动创建
  • 1.2.1、使用步骤
  • 测试文件
  • bean标签里面的数据库信息是写死的,如何解决
  • 1.引用外部的配置文件
  • 动态代理---作用:在不修改源码的情况下对方法进行增强
  • 2.3.基于接口的动态代理
  • 测试文件
  • 2.4、cglib的动态代理
  • 2.4.1、导入相关Jar包
  • 2.4.3、增强
  • 2.4.4、基于Cglib的动态代理
  • 2.5、使用代理工厂实现动态代理
  • 三、使用动态代理实现事务
  • 3.1、基础工程搭建
  • 3.1.1、建库建表
  • 3.1.2、导入Jar包
  • 3.1.3、创建account类
  • 3.1.4、创建AccountDao接口及其实现类
  • 3.1.5、创建AccountService接口及其实现类
  • 3.1.6、创建处理事务的工具类
  • 3.2、基于动态代理实现事务
  • 3.2.1、创建增强接口及其实现类
  • 3.2.2、创建代理工厂
  • 3.2.3、编写JDBC配置文件
  • 3.2.4、在Spring配置文件中进行配置


1.1、数据源(连接池)的作用

提高程序性能,事先在连接池中创建连接,使用连接资源时从数据源中获取,使用完毕后将连接归还到
连接池。
常用的数据源:C3P0,DBCP,Druid等。

1.2、数据源的手动创建

1.2.1、使用步骤

  1. 导入相关Jar包
  2. 新建数据源对象
  3. 设置数据源的基本参数
  4. 使用数据源获取连接和归还资源
    学了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包

spring高并发 动态数据源切换 spring动态数据源事务_spring高并发 动态数据源切换


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包

spring高并发 动态数据源切换 spring动态数据源事务_spring_02

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是回滚事务

spring高并发 动态数据源切换 spring动态数据源事务_spring_03

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; 再运行测试方法,程序能够回滚。