JavaEE中分层解耦合与事物控制-方案

本文探讨了JavaEE中分层架构的重要性,包括解耦合、业务拆分和接口升级的优点。作者介绍了其个人的分层模式,分为DAO、Service、Controller和View四层,并通过示例详细阐述了如何实现事务回滚,特别是在没有使用Hibernate的情况下,通过自定义注解和ThreadLocal进行事务管理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

JavaEE中分层解耦合与事物控制-方案


转载请注明出处:
http://blog.youkuaiyun.com/u010825468/article/details/49434803

0、写在前面

很多年前,依靠李兴华的《Java WEB开发实战经典》一书,开启了Java WEB之旅,个人认为这本书作为JavaEE入门非常不错,李兴华的视频讲的也很不错,或许大家现在都在看《*疯狂讲义》或是培训机构的教学视频,但经典始终是经典。

我在学习完JavaEE后没有真正从事过JavaEE开发的正式工作,大多只是平常自己写点东西,或是帮别人写点小应用之类的,两年后大学毕业,我也正式的转入了Android开发阵营,我对Android开发的理解大致为:如果我不在Android上做深入了解与学习,其实我就是个写前台的。但是好在有WEB的基础与WEB锻炼的Java技能,开发Android其实非常简单。

1、为什么要分层

学习过JavaEE基础的人都知道MVC,这是传统的JavaEE的设计模式,MVC即将工程或者说代码分成Model、View、Controller三层。Model即模型层,一般包括Java Bean以及这些bean对应的数据库的原子性操作方法(一般的CRUD、getAll、findByXXX、delByXXX),而这些数据库操作就是我们熟悉的DAO。我们一般将bean包命名为VO或者domain,dao操作包命名为dao。View即显示层,在JavaEE中可以将其看作Jsp,用于和用户产生动态交互的界面,是通过服务器编译才能进行显示的,关于Jsp的更多我们在后面详解。Controller即控制层,用来控制业务的流转,比如一个servlet,能够接受用户通过View提交的数据,然后根据自己的业务规则去调用不同的service(service属于控制层,是对每一个具体业务操作事物的封装,通过service调用dao),因此Controller是介于Model和View之间的桥梁,也是业务流转的核心。

解释了MVC,那我们为什么需要对代码结构进行分层呢?主要有以下几个方面的考虑:

  1. 解耦和:我们将一个业务流转和数据库操作分离开来,既符合面向对象的编程原则,又将代码逻辑整理的很清楚,在调试过程中也能方便改正BUG,体现了高内聚,低耦合的思想;
  2. 业务拆分:有时候我们一个接口需要处理的业务很复杂,通过分层能够将业务分为具体的某一类业务,然后对这一类业务的具体处理逻辑拆分为多次调用dao进行完成,让复杂的逻辑简单化了;
  3. 便于接口升级:各层之间通过接口进行解耦操作,也就是将接口和具体实现进行了分离,这样可以方便的实现或替换掉具体实现;
  4. 提高代码服用率:作为一个程序猿,我们不应该造重复的轮子,在一个系统中Model层是对数据库的原子性操作,复用性最高。假如我们更换了数据库,只要保证访问数据的接口方法不发生变化,我们也能够很快的实现对不同数据库的操作。

2、我的分层模式

个人一般将代码结构分为DAO、Service、Controller、View四层,dao和service都使用接口和真是实现类的方式,DAO、Service、Controller之间通过工厂类和配置文件进行解耦和。

3、Demo

在这个demo中我使用了struts2.3、c3p0、dbutils等常用的工具。包结构如下:
包结构

1.domain包里都是javabean,这里就不给代码了;

2.dao包里是对数据库的基础操作方法,其中有一个名为Dao的接口,只作为一个标识(后面在工厂类用到),没有定义方法和变量,以UserDao为例,UserDao接口继承自Dao(所有的dao类接口都需要继承自Dao),UserDaoImpl为真实实现类,继承自UserDaoDao。

    public interface UserDao extends Dao {
        /**
         * 根据用户名查找用户
         * @param username 用户名
         * @return 找到的用户bean,如果没找到返回null
         */
        User findUserByUName(String username);

        /**
         *  增加用户
         * @param user 封装了用户信息的bean
         */
        void addUser(User user);

        /**
         * 根据用户名 密码 查找用户
         * @param username 用户名
         * @param password 密码
         * @return 找到的用户 ,如果找不到返回null
         */
        User findUserByUNameAndPsw(String username, String password);

    ----------
}
    public class UserDaoImpl implements UserDao {
        @Override
        public User findUserByUName(String username) {
            String sql = "select * from users where userName = ?";
            try {
                QueryRunner runner = new QueryRunner(DaoUtils.getSource());
                return runner.query(sql, new BeanHandler<User>(User.class), username);
            } catch (SQLException e) {
                e.printStackTrace();
                throw new RuntimeException(e);
            }
        }

        @Override
        public void addUser(User user) {
            String sql = "insert into users values (null,?,?)";
            try {
                QueryRunner runner = new QueryRunner(DaoUtils.getSource());
                runner.update(sql, user.getUserName(), user.getPassword());
            } catch (SQLException e) {
                e.printStackTrace();
                throw new RuntimeException(e);
            }
        }

        ----------
    }

3.service包里是对应的原子性的业务的操作。同样,有一个不定义方法的service接口,UserService继承自Service接口,UserServiceImpl继承自UserService。

    public interface UserService extends Service {

        /**
         * 注册用户
         * @param user 封装了用户信息的bean
         */
        void registUser(User user);

        /**
         * 激活用户
         * @param activecode 激活码
         */
        void activeUser(String activecode);

        /**
         * 根据用户名 密码 查找用户
         * @param username 用户名
         * @param password 密码
         * @return 找到的用户 ,如果找不到返回null
         */
        User findUserByUNameAndPsw(String username, String password);

    }
    public class UserServiceImpl implements UserService {
        private UserDao dao = BasicFactory.getFactory().getInstance(UserDao.class);

        public void registUser(User user) {
        ----------
        }

        public void activeUser(String activecode) {
        ----------
        }

        public User findUserByUNameAndPsw(String username, String password) {
            return dao.findUserByUNameAndPsw(username,password);
        }

    }

4.action包是用户通过Jsp直接访问的控制类。定义一个UserAction,用户在进行注册操作时,该方法再调用service方法进行数据存储的具体操作:

    public class UserAction {
        UserService service = BasicFactory.getFactory().getInstance(UserService.class);
        private User user = new User();
        private String errorMsg;
        private User result_user;

        public User getResult_user() {
            service.registUser(user);
            ----------
            return xxx;
        }

        ----------
    }

以上操作就完成了一个简单的MVC模式(Jsp方面省略不说了)。上面的代码分层也是较为明确的了,但是设想,我们一个操作例如用户注册,由于数据库设计的原因,需要在多张数据库表中进行数据写入,但是其中有一次数据库操作失败了,导致注册失败。此时我们应该将事物回滚。

4、事物回滚

事物回滚是数据库操作中比较常见的一种操作方式,我们将一组业务的整体处理叫做一个事物,并且我们这一组业务在业务层面上具有原子性,既要么都成功,要么都失败。一旦在操作中途出现失败,必须将之前的数据操作进行回滚,将数据恢复到原始状态。

我们对事物的管理一般是基于JDBC Connection,在得到一个connection后,我们设置其AutoCommit为false:

    conn.setAutoCommit(false);

那么我们在这个Connection关闭之前的所有数据库操作都不会即时执行,知道指令:

    conn.commit();

才能将Connection执行的数据库操作真是操作在数据库中。

但是在hibernate中,实现了基于JTA的事物管理,也就是跨session即跨Connection生命周期的事物管理。通过JTA容器可以横跨多个JDBC Connection并忽略其生命周期的影响(JTA事物管理与JDBC事物管理不应该同时使用)。

我们在这个项目中没有用到hibernate而是使用c3p0作为数据源、dbutils作为数据库操作的工具类,并且在dao中使用QueryRunner进行数据库操作时,传入的是DataSource即一个数据源,那么问题来了:在service中进行一组数据库操作,如果需要进行事物控制怎么办?

通过上面对事物的分析,我们也有两种解决方案:

  1. 让这一组数据库操作拿到的是同一个JDBC Connection;
  2. 记录运行的sql,出现异常后通过补偿的sql进行回滚。

这两个方法都需要在service层进行处理。

我这里采用第一种方法进行事物控制:先贴代码

自定义一个注解:

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface Tran {
    }

BasicFactory类,用于解耦、数据库事物管理:

    public class BasicFactory {
        private static BasicFactory factory = new BasicFactory();

        private BasicFactory() {
        }

        public static BasicFactory getFactory() {
            return factory;
        }

        @SuppressWarnings("unchecked")
        public <T> T getInstance(Class<T> clz) {
            try {
                if (Service.class.isAssignableFrom(clz)) {
                    //--当前获取的是Service
                    String infClzName = clz.getSimpleName();
                    String impClzName = ConfigUtils.getProp(infClzName);
                    //--生成真正的Service对象
                    final T t = (T) Class.forName(impClzName).newInstance();
                    //--生成service对象的代理,在service方法执行之前和之后做事务的控制
                    T proxy = (T) Proxy.newProxyInstance(t.getClass().getClassLoader(), t.getClass().getInterfaces()
                            , new InvocationHandler() {
                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                            if (method.isAnnotationPresent(Tran.class)) {
                                //--当前被调用的方法上有@Tran,需要事务
                                try {
                                    TransactionManager.startTran();
                                    Object obj = method.invoke(t, args);
                                    TransactionManager.commit();
                                    return obj;
                                } catch (InvocationTargetException e) {
                                    TransactionManager.rollback();
                                    e.getTargetException().printStackTrace();
                                    throw new RuntimeException(e.getTargetException());
                                } catch (Exception e) {
                                    TransactionManager.rollback();
                                    e.printStackTrace();
                                    throw new RuntimeException(e);
                                } finally {
                                    TransactionManager.release();
                                }
                            } else {
                                //--当前被调用的方法上没有@Tran,不需要事务
                                return method.invoke(t, args);
                            }

                        }
                    });
                    return proxy;

                } else if (Dao.class.isAssignableFrom(clz)) {
                    //--当前获取的是Dao
                    String infClzName = clz.getSimpleName();
                    String impClzName = ConfigUtils.getProp(infClzName);
                    return (T) Class.forName(impClzName).newInstance();
                } else {
                    //既不是Service也不是Dao
                    throw new RuntimeException("参数错误");
                }
            } catch (Exception e) {
                e.printStackTrace();
                throw new RuntimeException(e);
            }
        }
    }

TransactionManager类,管理事务的管理类:

ThreadLocal可参照:关于ThreadLocal的理解

    public class TransactionManager {
        private static ThreadLocal<Connection> conn_local = new ThreadLocal<Connection>() {
            @Override
            protected Connection initialValue() {
                return DaoUtils.getConn();
            }
        };
        private static ThreadLocal<Boolean> hasTran_Local = new ThreadLocal<Boolean>() {
            protected Boolean initialValue() {
                return false;
            };
        };

        private TransactionManager() {
        }

        public static void startTran() {
            try {
                hasTran_Local.set(true);
                conn_local.get().setAutoCommit(false);
            } catch (SQLException e) {
                e.printStackTrace();
                throw new RuntimeException(e);
            }
        }

        public static void commit() {
            try {
                conn_local.get().commit();
            } catch (SQLException e) {
                e.printStackTrace();
                throw new RuntimeException(e);
            }
        }

        public static void rollback() {
            try {
                conn_local.get().rollback();
            } catch (SQLException e) {
                e.printStackTrace();
                throw new RuntimeException(e);
            }
        }

        public static Connection getConnection() {
            return conn_local.get();
        }

        public static boolean hasTran() {
            return hasTran_Local.get();
        }

        public static void release() {
            try {
                conn_local.get().close();
                conn_local.remove();
            } catch (SQLException e) {
                e.printStackTrace();
                throw new RuntimeException(e);
            }
        }
    }

DaoUtils类,用于获取数据库连接池以及连接的:

    public class DaoUtils {
        private static DataSource source = new ComboPooledDataSource();

        /*
            判断,
            如果当前线程没有开启过事务,返回普通的数据源
            如果开启过事务,返回改造过getConnetion方法的数据源,让getConnection方法每次调用都返回线程内部的开启过事务的那个Connection
         */
        public static DataSource getSource() {
            if (TransactionManager.hasTran()) {
                //--开过事务,返回改造过getConnection方法的数据源
                DataSource proxy = (DataSource) Proxy.newProxyInstance(source.getClass().getClassLoader(), source.getClass().getInterfaces()
                        , new InvocationHandler() {
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        if ("getConnection".equals(method.getName())) {
                            final Connection conn = TransactionManager.getConnection();
                            Connection conn_proxy = (Connection) Proxy.newProxyInstance(conn.getClass().getClassLoader(), conn.getClass().getInterfaces()
                                    , new InvocationHandler() {

                                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                                    if ("close".equals(method.getName())) {
                                        return null;
                                    } else {
                                        return method.invoke(conn, args);
                                    }
                                }

                            });
                            return conn_proxy;
                        } else {
                            return method.invoke(source, args);
                        }
                    }

                });
                return proxy;
            } else {
                //--没事务,返回普通数据源
                return source;
            }
        }

        public static Connection getConn() {
            try {
                return source.getConnection();
            } catch (SQLException e) {
                e.printStackTrace();
                throw new RuntimeException(e);
            }
        }

    }

工厂类所用到的配置文件:

UserDao=xxxxxx.dao.UserDaoImpl
UserService=xxxxxx.service.UserServiceImpl

从以上代码可以看出,我们定义的注解应该增加在service层的接口上。

在通过工厂类得到service的真实实现类的时候,如果判断service的方法被设置了tran这个注解,那么就会进入到我们的反射方法,我们在反射方法中真正执行方法method.invoke前后加上事物的控制就完成了整个service的事物控制。这里有个类非常重要ThreadLocal,如果不明白可以参见关于ThreadLocal的理解,我们在dao的真实实现类中使用

QueryRunner runner = new QueryRunner(DaoUtils.getSource());

其中DaoUtils.getSource()就是我们最为重要的,需要在一个线程中保持同一个的DataSource。

BasicFactory类在执行TransactionManager.startTran();的时候,实际执行的是:

    private static ThreadLocal<Connection> conn_local = new ThreadLocal<Connection>() {
        @Override
        protected Connection initialValue() {
            return DaoUtils.getConn();
        }
    };
    private static ThreadLocal<Boolean> hasTran_Local = new ThreadLocal<Boolean>() {
        protected Boolean initialValue() {
            return false;
        };
    };

    public static void startTran() {
        try {
            hasTran_Local.set(true);
            conn_local.get().setAutoCommit(false);
        } catch (SQLException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }

即此时已经给这个线程标记了一个hasTran_Local的布尔型的属性,true为执行事物。变量conn_local默认值是DaoUtils.getConn();即得到一个JDBC的连接,而我们在dao真是实现类里面通过DaoUtils.getSource()得到数据源时,我们通过反射得到数据源的代理,并且设置如果访问getConnection()方法是调用Connection conn = TransactionManager.getConnection();方法,而在TransactionManager.getConnection();方法中实际执行如下:

public static Connection getConnection() {
    return conn_local.get();
}

这里只是通过conn_local.get()得到一个JDBC的Connection对象,根据ThreadLocal的特性,我们没有调用set()方法,所以只有在这次调用get()方法时就会调用initialValue()方法,去DaoUtils里得到一个JDBC的connection。因此,这是DaoUtils的getSource()方法中的代码:

Connection conn = TransactionManager.getConnection();

得到的实际是当前source的connection。这样就完成了在一个线程中存储一个JDBC的Connection的操作。

那么在DaoUtils.getSource()方法中,为什么我们又要对在线程中得到的connection进行反射呢?

    Connection conn_proxy = (Connection) Proxy.newProxyInstance(conn.getClass().getClassLoader(), conn.getClass().getInterfaces()
                                , new InvocationHandler() {

                            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                                if ("close".equals(method.getName())) {
                                    return null;
                                } else {
                                    return method.invoke(conn, args);
                                }
                            }

                        });

由于我们现在使用的是c3p0数据源,它在使用完数据源后会将这个数据源的connection给close掉,从而在下一次取出一个数据源中的DataSource后才能正常使用。

因此,我们并不希望c3p0帮我们关闭了连接,因此我们在返回的DataSource的connection中做反射,当调用其close方法时,不真正的执行close方法。而是通过TransactionManager.release()方法真正的关闭一个JDBC的connection。
转载请注明出处:
http://blog.youkuaiyun.com/u010825468/article/details/49434803

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值