SpringIoC和事务的实现

IoC

什么是IoC?

IoC (Inversion of Control) 被称之为 控制反转反转控制,是一种技术思想(理论),而不是一种技术实现,只不过spring 在技术层面上完美地实现了而已

描述的事情:Java 开发领域 对象的创建、管理的问题

传统的开发方式:比如类A依赖于类B,往往会在类A中 new 一个B的对象

而在IoC思想中,我们不需要自己手动new对象,而是由IoC容器去帮助我们实例化并管理,我们需要使用哪个对象,就问IoC容器要即可

此时我们丧失了一个权力:创建和管理对象的权力,但是我们得到了一个福利:不用考虑对象的创建和管理等一系列的事情

而这个IoC容器,就是Spring框架实现的

为什么叫控制反转?

  1. 控制:对象创建(实例化、管理)的权力
  2. 反转:控制权交给外部环境(IoC容器)

IoC解决了什么问题?

IoC解决了对象之间的耦合问题

假设我们拥有一个Dao层和一个Service层

Dao:UserDao、UserDaoImpl
Service:UserService、UserServiceImpl

在UserServiceImpl中处理业务,必然要使用到UserDaoImpl,此时需要先构建其对象:UserDao userDao = new UserDaoImpl();

创建对象完成后,注意,此时的UserSerivceImpl 就和 UserDaoImpl 耦合在一起了,耦合性太高,一方面,二者其中之一发生异常,两个服务都将无法正常执行,另一方面,在UserDaoImpl的业务发生修改,UserServiceImpl也必然要一起发生修改。在任一业务发生修改,另一业务的变化会随着修改而变大时,这就称之为耦合度过高,而代码就是追求低耦合高内聚

在这里插入图片描述

IoC和DI的区别?

DI:Dependancy Injection(依赖注入)

IoC 和 DI 描述的是同一件事(对象创建和依赖关系维护),只是角度不一样

IoC角度:站在对象角度,对象实例化及其管理的权力交给了容器

DI角度:站在容器角度,容器会把对象依赖的其他对象注入(实例化A对象过程中,因为声明了B类型属性,就需要容器把B对象注入给A)

AOP 思想:切面编程

什么是AOP?

AOP:Aspect oriented Programming 面向切面编程/面向方面编程

AOP 是 OOP 的延续

OOP:面向对象三大特征:封装、继承、多态

OOP 解决的是多个类中 相同位置出现相同代码的问题,但是要是在方法中 相同位置出现相同代码,OOP就无法解决,而OOP的衍生 AOP 就是用来解决这个问题的(方法中同位置出现重复代码叫做横切逻辑代码)

AOP解决的什么问题?

在原有业务不变的情况下,增强横切逻辑代码,从根本上解耦合,避免横切逻辑代码重复

为什么叫面向切面编程?

切:横切逻辑,原有业务逻辑不能动,只能操作横切逻辑代码,所以面向切面逻辑

面:横切逻辑往往要影响的是多个方法,每一个方法如同一个点,多个点则构成面,有一个面的概念在其中

手写实现 IoC 和 AOP

分析

在项目中,最大的问题实际上是出现在new 关键字上,service层的实现类 ServiceImpl 和 Dao 层的具体实现类 DaoImpl 耦合在了一起,当需要切换Dao层实现类的时候必须要修改service 代码,不符合面向接口开发的最优原则

一、 除了 new 以外的技术还有什么技术可以实例化对象?

反射

Class.forName(“全限定名”);

全限定名:xxx.xxx.xxx.xxxDaoImpl

反射通过查找全限定名来实例化对象,但是DaoImpl修改了,全限定名又要一个一个重新指定,因此,全限定名应该被设定在 xml 中进行统一管理,更改 xml 一个全限定名即可更改所有反射的全限定名

二、 当需要实例化大量相同的对象时,可以使用工厂设计模式来通过反射技术生产对象

工厂模式是解耦合非常好的一种方式

工厂类只需要 读取解析 xml,然后通过反射技术实例化对象给外部提供获取对象的接口方法,而ServiceImpl直接向工厂类索要Dao层的实现类对象即可

实现对象解耦合

实现 自定义 xml:

<?xml version="1.0" encoding="UTF-8" ?>
<!--根标签beans,里面配置若干个bean子标签,每个bean子标签都代表一个类的配置-->
<beans>
    <!--在将来解析xml时,我们只需要将bean的class名解析为全限定名即可-->
    <!--在Service实现类索要对象时,需要有能够分辨对象的一个标识,我们将bean的id解析为标识即可-->
    <!--id用来给出去,class用来拿进来实例化-->
    <!--简单来讲:id标识对象,class锁定全限定名-->
	<bean id="标识" class="xxx.xxx.xxxImpl"></bean>
</beans>

实现工厂类:

/**
 *工厂类 生产对象(使用反射技术)
 *实际上也是工厂模式的一个应用
*/
public class BeanFactory{
    //1.首先要读取解析 xml,通过反射技术实例化对象并且存储待用(map集合)(饿汉模式实现,需要JVM加载时就执行)
    //2.对外提供获取实例对象的接口(根据 id 获取)
    
    private static Map<String,Object> map = new HashMap<>();//存储对象
    
    //饿汉式实现读取解析xml
    static{
        //1.读取xml
        InputStream resourceAsStream = BeanFactory.class.getClassLoader().getResourceAsStream("beans.xml");
        //2.解析xml(dom4j依赖实现,xpath表达式依赖快速定位xml元素)
        SAXReader saxReader = new SAXReader();
        try{
            Document document = saxReader.read(resourceAsStream);//将xml文件以流的方式读取
            Element rootElement = document.getRootElement();//获取xml流的根节点
            List<Element> beanList = rootElement.selectNodes("//bean");//查找具体的节点列表,这里查找所有的bean节点
            for(int i = 0;i < beanList.size(); i++){
                Element element = beanList.get(i);//<bean id="标识" class="xxx.xxx.xxxDaoImpl">
                //处理每一个bean元素,获取到元素的id和class
                String id = element.attributeValue("id");
                String clazz = element.attributeValue("class");
                //通过反射技术获取全限定名(clazz)来创建对象
                Class<?> aClass = Class.forName(clazz);
                Object o = aClass.newInstance();//实例化之后的对象
                
                //存储到map中待用
                map.put(id,o);
            }
            //实例化完成之后维护对象的依赖管理,检查哪些对象需要传值进入,根据它的配置,传入相应的值
            //有property子元素的bean就有传值需求
            List<Element> propertyList = rootElement.selectNodes("//property");
            //解析property
            for(int i = 0;i< propertyList.size();i++){
                Element element = propertyList.get(i);//<property name="setxxxDao" ref="accountDao"></property>
                String name = element.attributeValue("name");
                String ref = element.attributeValue("ref");
                
                //找到当前需要处理依赖关系的bean,实际上就是父节点
                Element parent = element.getParent();
                
                //调用父元素对象的反射功能
                String parentId = parent.attributeVlaue("id");
                Object parentObject = map.get(parentId);
                //遍历父对象中的方法,找到"set" + name属性的方法
                Method[] methods = parent.getClass().getMethods();
                for(int j = 0;j < methods.length;j++){
                    Method method = methods[j];
                    if(method.getName().equalsIgnoreCase("set" + name)){//该方法就是set方法
                        method.invoke(parentObject,map.get(ref));
                    }
                }
                
                //注意:把处理之后的parentObject重新放到map中
                map.put(parentId,parentObject);
            }
        }catch(DocumentException e){
            e.printStackTrace();
        }
        
        //对外提供获取实例对象的接口(根据id获取)
        public static Object getBean(String id){
            return map.get(id);
        }
        
        }
    }
    
}

spring主要的注入方式之一:set注入,也叫构造器注入:

我们在xml中定义新的标签, 在bean标签内定义一个property标签,使工厂类捕捉到property标签的name,以name来作为对应bean对象的set方法,具体实现如下:

<?xml version="1.0" encoding="UTF-8" ?>
<!--根标签beans,里面配置若干个bean子标签,每个bean子标签都代表一个类的配置-->
<beans>
    <!--在将来解析xml时,我们只需要将bean的class名解析为全限定名即可-->
    <!--在Service实现类索要对象时,需要有能够分辨对象的一个标识,我们将bean的id解析为标识即可-->
    <!--id用来给出去,class用来拿进来实例化-->
    <!--简单来讲:id标识对象,class锁定全限定名-->
	<bean id="标识" class="xxx.xxx.xxxDaoImpl">
    	<!--在bean标签下追加property子标签-->
		<!--set + name之后锁定了set方法,通过反射可以调用对应方法传入对应值-->
        <property name="xxxDao" ref="accountDao"></property>
    </bean>
</beans>

Spring事务控制

当存在一个业务时:

puiblic void treansfer(String fromCardNo,String toCardNo,int money) throw Exception{
	Account from = accountDao.queryAccountByCardNo(fromCardNo);
	Account to = accountDao.queryAccountByCardNo(toCardNo);
	
	form.setMoney(from.getMoney()-money);
	to.setMoney(to.getMoney()+money);
	
	accountDao.updateAccountByCardNo(to);
	int c = 1/0;
	accountDao.updateAccountByCardNo(from);
}

在为两条数据进行更新时发生异常,此时增加金额的数据正常执行,但是减少金额的数据由于异常被终止了

数据库事务归根结底是 Connection 的事务

connection.commit();提交事务

connection.rollback();回滚事务

在任何代码都没有使用connection.commit()时,JDBC依旧能够提交事务,是因为JDBC默认自动提交,可以通过setAutoCommit(Boolean)来决定是否自动提交

Connection con = DruidUnitls.getInstance().getConnection();//阿里的德鲁伊连接池
con.setAutoCommit(false)

此时就完成了关闭自动提交

此时衍生出来一些问题:

  • 增加金额和减少金额是两个事务,等同于两个Connection,要怎么样统一控制呢?
  • 事务控制目前是在Dao层,没有控制在service层

解决思路:

  • 让两次事务使用同一个Connection
    • 线程解决:两次事务均属于同一个线程内的执行调用,我们可以给当前线程绑定一个Connection,和当前线程有关系的数据库操作都去使用这个Connection(从当前线程中拿)
  • 把事务添加在service层
思路实现

原先的JdbcDao层:

public class JdbcAccountDaoImpl implements AccountDao{
    @Override
    public Account queryAccountByCardNo(String cardNo) throws Exception{
       //从连接池获取连接
        Connection con = DruidUtils.getInstance().getConnection();
        String sql = "select * from xxx where cardNo=?";
        PreparedStatement perparedStatement = con.preparStatement(sql);
        preparedStatement.getString(1,cardNo);
        ResultSet resultSet = preparedStatement.executeQuery();
        
        Account account = new Account();
        while(resultSet.next()){
            account.setCardNo(resultSet.getString("cardNo"));
            account.setName(result.getString("name"));
            account.setMoney(resultSet.getInt("money"));
        }
        resultSet.close();
        preparedStatement.close();
        con.close();
        
        return account;
    }
    
    @Override
    public int updateAccountByCardNo(Account account)throws Exception{
        //从连接池获取连接
        //改造为:从当前线程中获取绑定的connection连接
        Connection con = DruidUtils.getInstance().getConnection();
        String sql = "update account set money=? where cardNo=?"
        PreparedStatement preparedStatement = con.preparedStatement(sql);
        preparedStatement.setInt(1,account.getMoney());
        preparedStatement.setString(2,account.getCardNo());
        int i = preparedStatement.executeUpdate();

        preparedStatement.close();
        con.close();
        return i;
    }
}

创建一个工具类,用于获取线程中的connection连接

public class ConnectionUtils{
    
    private ConnectionUtils(){
    
}
    private static ConnectionUtils connectionUtils = new ConnectionUtils();
    public static ConnectionUtils getInstance(){
        return connectionUtils;
    }
    
    private ThreadLocal<Connection> threadLocal = new ThreadLocal<>();//存储当前线程的连接
    
    //从当前线程获取连接
    public Connection getCurrentThreadConn(){
        //判断当前线程是否已经绑定连接,没有绑定就需要从连接池中获取一个连接绑定到当前连接
        Connection connection = threadLocal.get();
        if(connection == null){
            //从连接池拿连接并绑定到当前线程
            connection = DruidUtils.getInstace().getConnection();
            //绑定到当前线程
            threadLocal.set(connection);
        }
        return connection;
    }
}

然后对JdbcDao层进行修改:

public class JdbcAccountDaoImpl implements AccountDao{
    @Override
    public Account queryAccountByCardNo(String cardNo) throws Exception{
       //从连接池获取连接
        //Connection con = DruidUtils.getInstance().getConnection();//修改为调用连接工具类的当前线程
        Connection con = ConnectionUtils.getInstance().getCurrentThreadConn();//修改后
        String sql = "select * from xxx where cardNo=?";
        PreparedStatement perparedStatement = con.preparStatement(sql);
        preparedStatement.getString(1,cardNo);
        ResultSet resultSet = preparedStatement.executeQuery();
        
        Account account = new Account();
        while(resultSet.next()){
            account.setCardNo(resultSet.getString("cardNo"));
            account.setName(result.getString("name"));
            account.setMoney(resultSet.getInt("money"));
        }
        resultSet.close();
        preparedStatement.close();
        //con.close();//取消关闭线程,让线程一直存在
        
        return account;
    }
    
    @Override
    public int updateAccountByCardNo(Account account)throws Exception{
        //从连接池获取连接
        //改造为:从当前线程中获取绑定的connection连接
        //Connection con = DruidUtils.getInstance().getConnection();//修改
        Connection con = ConnectionUtils.getInstance().getCurrentThreadConn();//修改后
        String sql = "update account set money=? where cardNo=?"
        PreparedStatement preparedStatement = con.preparedStatement(sql);
        preparedStatement.setInt(1,account.getMoney());
        preparedStatement.setString(2,account.getCardNo());
        int i = preparedStatement.executeUpdate();

        preparedStatement.close();
        //con.close();//取消释放线程,让线程一直存在
        return i;
    }
}

最后我们来完成事务控制,即开启事务、提交事务和回滚事务,首先拿到我们最开始的业务,对这个业务进行修改:

puiblic void treansfer(String fromCardNo,String toCardNo,int money) throw Exception{
	try{
        //开启事务(关闭事务的自动提交)
        ConnectionUtils.getInstance().getCurrentThreadConn().getAutoCommit(false);//将放入事务管理器中
        
        //不动的逻辑
        Account from = accountDao.queryAccountByCardNo(fromCardNo);
        Account to = accountDao.queryAccountByCardNo(toCardNo);

        form.setMoney(from.getMoney()-money);
        to.setMoney(to.getMoney()+money);

        accountDao.updateAccountByCardNo(to);
        int c = 1/0;
        accountDao.updateAccountByCardNo(from);
        
        //提交事务
        ConnectionUtils.getInstance().getCurrentThreadConn().commit();//将放入事务管理器中
    }catch (Exception e){
        e.printStackTrace();
        //回滚事务
        ConnectionUtils.getInstance().getCurrentThreadConn().rollback();//将放入事务管理器中
        
        throw e;//捕获了异常一定要抛出,便于上层捕获该异常
    }
}

拓展代码思路:开启事务、提交事务和回滚事务实际上可以封装为一个类,作为一个管理器,实际上就是应用了面向对象思想

//事务管理器:负责手动事务的开启、提交、回滚
public class TransactionManager{
    
    private TransactionManager();
    
    private static TransactionManager transactionManager = new TransactionaManager();
    
    public static TransactionManager getInstance(){
        return transactionManager;
    }
    
    //开启事务
    public void beginTransaction() throws SQLException {
        ConnectionUtils.getInstance().getCurrentThreadConn().getAutoCommit(false);//放入了管理器中
    }
    
    //提交事务
    public void commit() throws SQLException {
        ConnectionUtils.getInstance().getCurrentThreadConn().commit();//放入了管理器中
    }
    
    //回滚事务
    public void rollback() throws SQLException {
        ConnectionUtils.getInstance().getCurrentThreadConn().rollback();//放入了管理器中
    }
}

此时业务层变成了:

puiblic void treansfer(String fromCardNo,String toCardNo,int money) throw Exception{
	try{
        //开启事务(关闭事务的自动提交)变更
        TransactionManager.getInstance().beginTransaction();
        
        //不动的逻辑
        Account from = accountDao.queryAccountByCardNo(fromCardNo);
        Account to = accountDao.queryAccountByCardNo(toCardNo);

        form.setMoney(from.getMoney()-money);
        to.setMoney(to.getMoney()+money);

        accountDao.updateAccountByCardNo(to);
        int c = 1/0;
        accountDao.updateAccountByCardNo(from);
        
        //提交事务 变更
        TransactionManager.getInstance().commit();
    }catch (Exception e){
        e.printStackTrace();
        //回滚事务 变更
        TransactionManager.getInstance().rollback();
        
        throw e;//捕获了异常一定要抛出,便于上层捕获该异常
    }
}

下一篇:AOP-代理模式-动态代理

  • 17
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

.英杰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值