自定义事务注解[通俗易懂]

用过spring的,应该都知道@transactional这个注解。这个注解给我们日常开发带来了很大便利,让我们无需为了数据的提交和回滚操心,只需要关注自己的业务逻辑即可。这里我模拟一下spring的注解是如何执行的。这个是项目结构首先我们需要一个数据库的连接为了保证事务的完整性,这里我们用ThreadLocal保存当前线程的数据库连接/***Createdbyzh…

大家好,又见面了,我是你们的朋友全栈君。

用过spring的,应该都知道@transactional这个注解。这个注解给我们日常开发带来了很大便利,让我们无需为了数据的提交和回滚操心,只需要关注自己的业务逻辑即可。这里我模拟一下spring的注解是如何执行的。

这个是项目结构

自定义事务注解[通俗易懂]

首先我们需要一个数据库的连接

为了保证事务的完整性,这里我们用ThreadLocal保存当前线程的数据库连接

/**
 * Created by zhoum on 2019-06-28.
 */
public class ConnectionUtils {
    private static DruidDataSource dataSource = new DruidDataSource();
    private static ThreadLocal<Connection> threadLocal = new ThreadLocal<>();

    //静态代码块,设置连接数据库的参数
    static {
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/web");
        dataSource.setUsername("root");
        dataSource.setPassword("root");
    }


    public static Connection getConection() throws SQLException {
        Connection conn = threadLocal.get();
        if (conn == null) {
            conn = dataSource.getConnection();
            threadLocal.set(conn);
        }
        return conn;
    }
}

说到自定义事务注解,肯定是要用自己写的注解

新建一个自己的事务注解

/**
 * Created by zhoum on 2019-06-28.
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Transactional {

}

接下来,新建一个业务类,也就是我们常常写service

我在类上打上刚刚新建的自定义事务注解

/**
 * Created by zhoum on 2019-06-28.
 */
@Transactional
public class UserServiceImpl implements UserService {
    @Override
    public void add(User user) throws SQLException {
        Connection conn = ConnectionUtils.getConection();
        PreparedStatement preparedStatement = conn.prepareStatement("insert into USER (name,age)" +
                " VALUES (?,?)");
        preparedStatement.setObject(1, user.getName());
        preparedStatement.setObject(2, user.getAge());
        preparedStatement.execute();
        preparedStatement = conn.prepareStatement("insert into USER (name,age)" +
                " VALUES (?,?)");
        preparedStatement.setObject(1, user.getName());
        preparedStatement.setObject(2, user.getAge());
        preparedStatement.execute();
    }
}

接下来新建一个bean的工厂类,这就像我们找spring要bean一样,我们在xml或是注解上定义这个bean的基本信息,spring为我们创建这些bean并保存,当我需要时,只要打上autowire就可以拿到了

/**
 * Created by zhoum on 2019-07-02.
 */
public class BeanFactory {
    public static Object getBean(Class clazz) throws IllegalAccessException, InstantiationException, SQLException, NoSuchMethodException, InvocationTargetException {
        Object o = clazz.newInstance();
        if (o.getClass().isAnnotationPresent(Transactional.class)) {
            Connection conection = ConnectionUtils.getConection();
            ProxyBean proxyBean = new ProxyBean(o, conection);
            return proxyBean.getBean();
        }
        return o;
    }
}

代码中有一行 ProxyBean ,这个类是专门用来获取代理对象的。

target是当前目标执行类,connection是当前数据库连接

我们将UserServiceImpl进行代理,将connection开启事务,提交,回滚的代码把代理对象的add方法夹住


/**
 * Created by zhoum on 2019-07-02.
 */
public class ProxyBean {

    private Object target;

    private Connection connection;

    public ProxyBean(Object target, Connection connection) {
        this.target = target;
        this.connection = connection;
    }

    public Object getBean() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Object o = Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                connection.setAutoCommit(false);
                try {
                    method.invoke(target, args);
                    connection.commit();
                } catch (Exception e) {
                    connection.rollback();
                } finally {
                    connection.close();
                }
                return null;
            }
        });
        return o;
    }
}

最后一步就是测试


/**
 * Created by zhoum on 2019-06-28.
 */
public class Main {
    public static void main(String[] args) throws SQLException, InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {
        UserService bean = (UserService) BeanFactory.getBean(UserServiceImpl.class);
        User user = new User();
        user.setName("伟哥");
        user.setAge(1);
        bean.add(user);
    }
}

1:没打上自定义事务注解

执行run main方法,可以看到成功插入了2条

自定义事务注解[通俗易懂]

接下来我们在UserServiceImpl.add()方法加上异常代码,可以看到只添加了一条数据

自定义事务注解[通俗易懂]

自定义事务注解[通俗易懂]

说明没有加上@transactional注解的UserServiceImpl不是我们动态代理的事务对象。所以事务并没有生效

2:打上自定义事务注解

没有加上任何异常代码,正常执行,成功添加2条记录

自定义事务注解[通俗易懂]

自定义事务注解[通俗易懂]

在UserServiceImpl.add()方法加入异常代码,可以看到没有新增任何数据,说明事务生效了。

自定义事务注解[通俗易懂]

自定义事务注解[通俗易懂]

总结

其实spring提供的事务并没有那么难理解,无非就是利用jdk或是cglib的动态代理,帮你代理一个对象,并对这个对象做一些加工。当我们找spring要这个对象时,spring给我们的是这个对象的代理对象。

当我把UserServiceImpl类上的transactional注解删除时,我得到对象就是正常new出来的对象。

当加上时,得到的就是动态代理的对象。其实这跟spring给我对象时是一样,我们把项目中的类spring提供的transactional删除后,得到对象就是普通new出来的对象吗,加上后spring才会为我们去动态代理这些对象

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/129033.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • Codeforces Round #FF (Div. 2):C. DZY Loves Sequences[通俗易懂]

    Codeforces Round #FF (Div. 2):C. DZY Loves Sequences

    2022年1月25日
    42
  • 实现关联条件的casewhen效果

    实现关联条件的casewhen效果 selectp.pcmxid,    j.csjtf*p.jsrs+j.dpf*p.jsrs+j.zsf*p.jsrs*p.sjts+    j.clbzf*p.jsrs*p.sjts+j.bxf*p.jsrs*p.sjtsasjsxj frompcmxp   leftjoin(—-关联一个过渡表 sele…

    2025年9月19日
    6
  • 英语商务邮件常用句子_商务英语email写作

    英语商务邮件常用句子_商务英语email写作1.Iamwritingtoconfirm/enquire/informyou…我发邮件是想找你确认/询问/想通知你有关…2.IamwritingtofollowuponourearlierdecisiononthemarketingcampaigninQ2.我写邮件来是为了跟进我们之前对第二季度营销活动的决定。3

    2022年10月20日
    2
  • 手机号码归属地数据库下载

    手机号码归属地数据库下载一份手机号归属地数据,共有174495条数据,包括中国移动,中国联通,中国电信的(包含152、186、188、189开头的手机号)。其中cardtype字段基本上没有什么用。因为可以通过办理某些业务就可以把如全球通卡转成神州行卡。对于这样的一份数据,你想要怎么利用?如果你只是想做一个手机号归属地查询的功能,那就浪费了。这个收数据库的主要功能就是通过手机号的前7位数字判断出手机号的归属城…

    2022年7月22日
    48
  • Robo 3T安装及使用

    Robo 3T安装及使用Robo3T 是一款叫 mongodb 可视化工具 是一个免费版本 还有个付费版本叫 Studio3T 下载地址 https robomongo org download 选择适合你的 我选择 exe 下载好后解压 创建快捷键 打开窗口再 ctrl N 快捷键打开连接窗口 点击 connect 连接连接成功 会显示 admin local config test 四个数据库 你之前创建的一般保存在 test 设置时间显示在未设置前数据内的时间数都比当前时间少 8 小时 比如 2019 2 2715

    2025年10月17日
    2
  • c++ offsetof_函数offset的用法

    c++ offsetof_函数offset的用法宏offsetof标准库stddef.h定义size_toffsetof(type,member);分析C库宏offsetof(type,member)会生成一个类型为size_t的整型常量,它是一个结构成员(member)相对于结构(type)开头的字节偏移量。成员是由member给定的,结构的名称是在type中给定的。type–这是一个参数class类型,是结…

    2022年8月22日
    9

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号