You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

扩展jOOQ自动生成DAO时遇只读事务INSERT执行失败问题

Spring Boot + jOOQ 扩展DAO引发只读事务异常问题解答

问题背景

我在Spring Boot应用中使用PostgreSQL数据库与jOOQ已有一段时间,使用jOOQ代码生成工具生成DAO、POJO等,当前使用spring-boot-starter-jooq 3.2.2版本(依赖jOOQ 3.18.9)。

为扩展自动生成的DAO,实现了如下代码:

@Repository
public class SomeRepositoryImpl extends SomeDao {

    private final DSLContext dslContext;

    public SomeRepositoryImpl(DSLContext dslContext) {
        super(dslContext.configuration());
        this.dslContext = dslContext;
    }

    
    public void save(Example example) {
        insert(example);
    }
}

其中SomeDao、Example分别为自动生成的DAO与POJO,insert()方法来自jOOQ自动生成DAO的父类。

当从服务层调用someRepositoryImpl.save(example)时,抛出如下错误:

Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: org.jooq.exception.DataAccessException: SQL [insert into "public"."example" ("name", "domain", "description", "website", "language_id") values (?, ?, ?, ?, ?) returning "public"."example"."example_id"]; ERROR: cannot execute INSERT in a read-only transaction] with root cause

org.postgresql.util.PSQLException: ERROR: cannot execute INSERT in a read-only transaction
    at org.postgresql.core.v3.QueryExecutorImpl.receiveErrorResponse
and so on....

我并未在代码中使用@Transactional(readOnly = true)注解,但在SomeRepositoryImpl的save方法上添加@Transactional注解后代码可正常运行;另外,直接在服务层使用自动生成的SomeDao也可正常执行插入操作:

@Autowired
SomeDao someDao;

someMethod(Example example){
someDao.insert(example);
}

问题解答

1. 导致异常的原因

Spring Boot对jOOQ自动生成的DAO类(比如SomeDao)有内置的事务支持逻辑:这些DAO会被Spring自动创建事务代理,当调用其方法时,Spring会默认开启读写事务(除非显式指定只读)。

而你自定义的SomeRepositoryImpl虽然继承了自动生成的DAO,但Spring不会自动给它的自定义方法(比如save)创建事务代理——因为你没有给该方法添加@Transactional注解。此时调用insert方法时,底层数据库连接可能处于只读事务上下文(比如外层调用链存在未显式标注的只读事务,或者Spring默认的事务传播行为导致继承了只读属性),而jOOQ的insert操作需要读写权限,因此抛出"cannot execute INSERT in a read-only transaction"异常。

直接使用自动生成的SomeDao时,Spring的jOOQ自动配置会为其方法默认提供事务支持,所以insert操作能正常执行。

2. 扩展DAO的最佳实践

这种继承自动生成DAO的扩展方式是可行的,但并非最优解,推荐根据场景选择以下方案:

  • 方案一:继承扩展(需注意事务注解)
    如果只是简单封装通用操作(比如重命名方法),可以继续使用继承方式,但必须给自定义方法添加@Transactional注解,确保Spring为其创建读写事务代理。

  • 方案二:组合替代继承(推荐)
    不要继承自动生成的DAO,而是将其注入到自定义Repository中,通过组合方式使用:

    @Repository
    public class SomeRepositoryImpl {
        private final SomeDao someDao;
    
        public SomeRepositoryImpl(SomeDao someDao) {
            this.someDao = someDao;
        }
    
        @Transactional
        public void save(Example example) {
            someDao.insert(example);
        }
    }
    

    这种方式更灵活,避免继承带来的事务代理问题,同时保持自动生成代码的独立性(后续重新生成DAO代码不会影响你的自定义逻辑),也便于扩展复杂业务逻辑。

  • 方案三:直接使用DSLContext
    如果需要自定义复杂SQL或批量操作,可以直接在Repository中注入DSLContext,完全自主编写数据库操作逻辑,这种方式适合高度定制化的场景。

内容的提问来源于stack exchange,提问作者Anurag Singh

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 09:42:37