简体   繁体   English

jOOQ + Spring:PSQLException:当前事务中止,命令被忽略,直到事务结束

[英]jOOQ + Spring : PSQLException: current transaction is aborted, commands ignored until end of transaction

I'm developing a webapp which uses Spring and jOOQ. 我正在开发一个使用Spring和jOOQ的webapp。

Consider the following use case: 考虑以下用例:

  1. I open URL which renders result of the db query: select * from contract_ref , everything works ok. 我打开呈现数据库查询结果的URL: select * from contract_ref ,一切正常。

  2. I open URL which tries to run query which trigger Postgres error, ie select * from users_ref (table users_ref doesn't exist), and got error: 我打开尝试运行触发Postgres错误的查询的URL,即select * from users_ref (表users_ref不存在)中select * from users_ref ,并得到错误消息:

    Servlet.service() for servlet [dispatcher] in context with path [/astra] threw exception [Request processing failed; nested exception is org.springframework.jdbc.BadSqlGrammarException: jOOQ; bad SQL grammar [select * from "users_ref"]; nested exception is org.postgresql.util.PSQLException: ОШИБКА: отношение "users_ref" не существует Servlet.service() for servlet [dispatcher] in context with path [/astra] threw exception [Request processing failed; nested exception is org.springframework.jdbc.BadSqlGrammarException: jOOQ; bad SQL grammar [select * from "users_ref"]; nested exception is org.postgresql.util.PSQLException: ОШИБКА: отношение "users_ref" не существует . Servlet.service() for servlet [dispatcher] in context with path [/astra] threw exception [Request processing failed; nested exception is org.springframework.jdbc.BadSqlGrammarException: jOOQ; bad SQL grammar [select * from "users_ref"]; nested exception is org.postgresql.util.PSQLException: ОШИБКА: отношение "users_ref" не существует

  3. When I try to open page from step 1, I get the error PSQLException: current transaction is aborted, commands ignored until end of transaction block , but I see that webapp tries to execute statement from step 1. 当我尝试从步骤1打开页面时,出现错误PSQLException: current transaction is aborted, commands ignored until end of transaction block ,但是我看到webapp尝试从步骤1执行语句。

It's seems for me that the situation is like Postgres didn't close transaction, but I do only select and so there's no any transaction required. 在我看来,情况就像Postgres没有关闭交易,但我只select ,因此不需要任何交易。

When I add @Transactional annotation for ReferenceController.view behaviour described above disappears. 当我为ReferenceController.view添加@Transactional批注时,上述行为消失了。

Full log: http://pastebin.com/t3UmbeCy 完整日志: http : //pastebin.com/t3UmbeCy

applicationContext.xml: applicationContext.xml中:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:cache="http://www.springframework.org/schema/cache"
       xmlns:bean="http://www.springframework.org/schema/mvc"
       xmlns:tx="http://www.springframework.org/schema/tx"
       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 http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">

    <cache:annotation-driven/>

    <tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true" />

    <context:component-scan base-package="net.kerba"/>

    <context:property-placeholder location="classpath:config.properties"/>

    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="localDbDataSource"/>
    </bean>

    <bean id="jacksonMessageConverter"
          class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/>

    <bean:annotation-driven>
        <bean:message-converters>
            <ref bean="jacksonMessageConverter"/>
        </bean:message-converters>
    </bean:annotation-driven>


    <bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
        <property name="caches">
            <set>
                <bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean">
                    <property name="name" value="user-page"/>
                </bean>
            </set>
        </property>
    </bean>

    <bean id="viewResolver" class="org.springframework.web.servlet.view.tiles3.TilesViewResolver">
        <property name="requestContextAttribute" value="requestContext"/>
        <property name="viewClass" value="org.springframework.web.servlet.view.tiles3.TilesView"/>
    </bean>

    <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
        <property name="exceptionMappings">
            <props>
                <prop key="java.lang.NullPointerException">exception</prop> <!-- map exception to view name -->
                <prop key="org.springframework.jdbc.UncategorizedSQLException">exception</prop> <!-- map exception to view name -->
            </props>
        </property>
    </bean>

    <bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles3.TilesConfigurer">
        <property name="definitions">
            <list>
                <value>/WEB-INF/tiles/tiles-common.xml</value>
                <value>/WEB-INF/tiles/tiles-admin.xml</value>
                <value>/WEB-INF/tiles/tiles-requests.xml</value>
            </list>
        </property>
        <property name="preparerFactoryClass"
                  value="org.springframework.web.servlet.view.tiles3.SpringBeanPreparerFactory"/>
    </bean>

    <bean id="liquibase" class="liquibase.integration.spring.SpringLiquibase">
        <property name="dropFirst" value="true"/>
        <property name="dataSource" ref="localDbDataSource"/>
        <property name="changeLog" value="classpath:db.astra.index.xml"/>
    </bean>

    <bean id="localDbDataSource"
          class="org.apache.tomcat.jdbc.pool.DataSource">
        <property name="driverClassName" value="${db.driverClassName}"/>
        <property name="url" value="${db.url}"/>
        <property name="username" value="${db.username}"/>
        <property name="password" value="${db.password}"/>
        <property name="maxIdle" value="${db.maxIdle}"/>
        <property name="minIdle" value="${db.minIdle}"/>
        <property name="maxActive" value="${db.maxActive}"/>
        <property name="timeBetweenEvictionRunsMillis" value="${db.timeBetweenEvictionRunsMillis}"/>
        <property name="minEvictableIdleTimeMillis" value="${db.minEvictableIdleTimeMillis}"/>
        <property name="testWhileIdle" value="${db.testWhileIdle}"/>
        <property name="validationQuery" value="${db.validationQuery}"/>
        <property name="removeAbandoned" value="${db.removeAbandoned}"/>
        <property name="logAbandoned" value="${db.logAbandoned}"/>
        <property name="initialSize" value="${db.initialSize}"/>
        <property name="defaultAutoCommit" value="false" />
    </bean>

    <bean id="transactionAwareDataSource"
          class="org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy">
        <constructor-arg ref="localDbDataSource"/>
    </bean>

    <bean class="org.jooq.impl.DataSourceConnectionProvider" name="connectionProvider">
        <constructor-arg ref="transactionAwareDataSource"/>
    </bean>

    <bean id="exceptionTranslator"
          class="net.kerba.astra.exception.SpringExceptionTranslator"/>

    <bean class="org.jooq.impl.DefaultConfiguration" name="jooqConfig">
        <constructor-arg index="0" ref="connectionProvider"/>
        <constructor-arg index="1">
            <null/>
        </constructor-arg>
        <constructor-arg index="2">
            <null/>
        </constructor-arg>
        <constructor-arg index="3">
            <list>
                <bean class="org.jooq.impl.DefaultExecuteListenerProvider">
                    <constructor-arg index="0" ref="exceptionTranslator"/>
                </bean>
            </list>
        </constructor-arg>
        <constructor-arg index="4">
            <null/>
        </constructor-arg>
        <constructor-arg index="5">
            <value type="org.jooq.SQLDialect">POSTGRES</value>
        </constructor-arg>
        <constructor-arg index="6">
            <null/>
        </constructor-arg>
        <constructor-arg index="7">
            <null/>
        </constructor-arg>
    </bean>

    <bean id="dsl" class="org.jooq.impl.DefaultDSLContext">
        <constructor-arg ref="jooqConfig"/>
    </bean>

    <bean id="patientDao" class="net.kerba.astra.jooq.tables.daos.PatientDao">
        <property name="configuration" ref="jooqConfig" />
    </bean>

    <bean id="patientStatusDao" class="net.kerba.astra.jooq.tables.daos.PatientStatusRefDao">
        <property name="configuration" ref="jooqConfig" />
    </bean>

    <bean id="requestUrgencyRefDao" class="net.kerba.astra.jooq.tables.daos.RequestUrgencyRefDao">
        <property name="configuration" ref="jooqConfig" />
    </bean>

    <bean id="divisionRefDao" class="net.kerba.astra.jooq.tables.daos.DivisionRefDao">
        <property name="configuration" ref="jooqConfig" />
    </bean>

    <bean id="requestStateRefDao" class="net.kerba.astra.jooq.tables.daos.RequestStateRefDao">
        <property name="configuration" ref="jooqConfig" />
    </bean>
</beans>

ReferencesController: ReferencesController:

package net.kerba.astra.controller;

@Controller
@RequestMapping("references")
public class ReferencesController {

    private static final Logger logger = LoggerFactory.getLogger(ReferencesController.class);

    @Autowired
    private ReferenceService referenceService;

    @Autowired
    private DSLContext dsl;

    private static final Map<String,Object> REFERENCES_CONFIG = loadReferencesList();

    private static Map<String,Object> loadReferencesList() {
        final InputStream resourceAsStream = ReferencesController.class.getResourceAsStream("ReferencesController.data.json");
        Objects.requireNonNull(resourceAsStream, "resourceAsStream must not be null");

        final InputStreamReader json;

        try {
            json = new InputStreamReader(resourceAsStream, "utf8");
            Objects.requireNonNull(json, "json must not be null");
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException("Encoding not supported: utf8", e);
        }

        final Map map = new Gson().fromJson(json, Map.class);
        Objects.requireNonNull(map, "map must not be null");

        SortedMap<String,Object> sortedMap = new TreeMap(new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                String key1 = (String) o1;
                String key2 = (String) o2;

                if (key1 != null && key2 != null) {
                    return key1.compareTo(key2);
                } else {
                    return 0;
                }
            }
        });
        sortedMap.putAll(map);

        return sortedMap;
    }

    /**
     * Индексная страница справочников
     * @param model
     * @return
     */
    @RequestMapping(value = "", method = RequestMethod.GET)
    public String index(Model model) {
        model.addAttribute("referencesConfig", REFERENCES_CONFIG);
        model.addAttribute("pageTitle", "Справочники");
        return "references.index";
    }


    @RequestMapping(value = "view/{referenceName}", method = RequestMethod.GET)
    public String view(Model model,@PathVariable("referenceName") String referenceName) {
        model.addAttribute("referencesConfig", REFERENCES_CONFIG);

        if (!REFERENCES_CONFIG.containsKey(referenceName)) {
            model.addAttribute("pageTitle", "Справочник не найден");
            model.addAttribute("message", "Справочник не найден!");
            return "error";
        } else {
            final Map currentRef = (Map) REFERENCES_CONFIG.get(referenceName);
            model.addAttribute("pageTitle", "Справочник: «" + currentRef.get("name") + "»");
            model.addAttribute("selectedReferenceConfig", currentRef);
            model.addAttribute("selectedReferenceConfigKey", referenceName);

            SelectQuery selectQuery = dsl.selectQuery();
            final String tableName = currentRef.get("tableName").toString();
            Objects.requireNonNull(tableName, "tableName must not be null");
            selectQuery.addFrom(DSL.tableByName(tableName));
            final Result result = selectQuery.fetch();

            logger.info("result: {}", result.intoMaps());
            model.addAttribute("referenceData", result.intoMaps());
            return "references.index";
        }
    }
}

Transactions are always present with Postgres . Postgres 始终存在事务 There are various options to control them, such as explicit transactions ( BEGIN - END blocks in pure SQL ) versus implicit transactions , where they're not declared but implied. 有很多选项可以控制它们,例如显式事务 (纯SQL中的 BEGIN - END块)与隐式事务 (未声明但暗含)。

There's also autocommit , where the transaction is ended for you automatically after each statement -- ie COMMIT if successful or ROLLBACK if not. 还有一个autocommit ,在每个语句后自动为您结束事务-即如果成功则为COMMIT ,否则为ROLLBACK This doesn't allow for multiple separate commands under the same transaction, however, which is often desirable. 但是,这不允许在同一事务下使用多个单独的命令,这通常是合乎需要的。

jOOQ does not manage transactions at all, and defers to existing methods to manage them ( Spring TransactionAwareDataSourceProxy in your case). jOOQ根本不管理事务,而是使用现有方法来管理TransactionAwareDataSourceProxy在您的情况下为Spring TransactionAwareDataSourceProxy )。 Adding @Transactional defines for Spring the attributes of the transaction (you can do things like set the isolation level , among other things). Spring添加@Transactional定义事务的属性(您可以执行诸如设置隔离级别之类的操作)。

So if you are getting the expected behavior with that annotation in place, I think that's fine and to be expected, since, as I mentioned above, Postgres is always transactional. 因此,如果使用该批注就可以实现预期的行为,那么我认为这很好并且可以预期,因为如上所述, Postgres始终是事务性的。 Without that annotation, Spring doesn't treat the interaction with Postgres as if it's transactional, even though at the Postgres level it is, and thus you get the behavior such as trying to do additional queries on an aborted transaction, resulting in an exception. 没有该注释,Spring不会将与Postgres的交互视为事务性的,即使在Postgres级别上也是如此,因此您会遇到诸如尝试对异常终止的事务进行其他查询等行为,从而导致异常。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM