首页 > 解决方案 > EntityManager 使用依赖注入导致的死锁

问题描述

我正在开发一个带有 REST 服务的应用程序。我正在使用依赖注入来注入 EntityManagerFactory。我有一个繁重的过程,我想使用线程,但是在使用 EntityManager 时它给了我一个死锁。编码:

注意:所有代码已被简化并重命名类以解释问题

持久性.xml:

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.1"
    xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">

    <persistence-unit name="mypersistenceunit" transaction-type="RESOURCE_LOCAL">
        <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>

        <!-- Entities -->

        <properties>
            <property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect" />
            <property name="hibernate.show_sql" value="true" />
            <property name="hibernate.event.merge.entity_copy_observer" value="allow" />
            <property name="hibernate.connection.autocommit" value="false"/>
        </properties>
    </persistence-unit>
</persistence>

应用程序上下文.xml:

<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:tx="http://www.springframework.org/schema/tx"
    xmlns:jee="http://www.springframework.org/schema/jee"
    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-4.0.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd
        "
    default-autowire="byName">

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

    <bean id="logDao" class="my.package.example.dao.LogDao">
        <property name="entityManagerFactory" ref="entityManagerFactory" />
    </bean>

    <bean id="extractDataDao" class="my.package.example.source.ExtractDataDao">
        <property name="completeDatabaseModel" ref="completeDatabaseModel"/>
    </bean>

    <bean id="completeDatabaseModel" class="my.package.example.source.CompleteDatabaseModel">
        <property name="logDao" ref="logDao" />
    </bean>

    <bean id="fill" class="my.package.example.services.impl.FillDatabaseServiceImpl">
        <property name="extractDataDao" ref="extractDataDao" />
    </bean>

    <!-- Configuracion de acceso a base de datos -->
    <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="dataSource" ref="myDataSource" />
        <property name="persistenceUnitName" value="mypersistenceunit"/>
        <property name="jpaVendorAdapter">
            <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
                <property name="showSql" value="false" />
                <property name="databasePlatform" value="org.hibernate.dialect.MySQLDialect" />
            </bean>
        </property>
    </bean>

    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver" />
    </bean>

    <bean id="myDataSource" parent="dataSource">
        <property name="url" value="${jdbc.url}" />
        <property name="username" value="${jdbc.username}" />
        <property name="password" value="${jdbc.password}" />
    </bean>

    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
       <property name="entityManagerFactory" ref="entityManagerFactory" />
   </bean>

    <tx:annotation-driven transaction-manager="transactionManager" />   

</beans>

FillDatabaseServiceImpl.java:

public class FillDatabaseServiceImpl implements FillDatabaseService {

    private ExtractDataDao extractDataDao;

    private final static Gson gson = new Gson();

    @Override
    public Response start() {
        ResultDto resultDto = new ResultDto();

        Thread thread = new Thread(this.extractDataDao);
        thread.start();

        resultDto.setData("Process has started");

        return Response.ok(gson.toJson(resultDto)).build();
    }

    public void setExtractDataDao(ExtractDataDao extractDataDao) {
        this.extractDataDao = extractDataDao;
    }

}

ExtractDataDao.java:

public class ExtractDataDao implements Runnable {

    private CompleteDatabaseModel completeDatabaseModel;

    @Override
    public void run() {
        // Very simplifed class
        int numThreads = 5;
        ExecutorService executor = Executors.newFixedThreadPool(numThreads);
        for (int j = 0; j < numThreads; j++) {
            executor.execute(this.completeDatabaseModel);
        }
    }

    public void setCompleteDatabaseModel(CompleteDatabaseModel completeDatabaseModel) {
        this.completeDatabaseModel = completeDatabaseModel;
    }
}

CompleteDatabaseModel.java

public class CompleteDatabaseModel implements Runnable {

    private EntityManagerFactory entityManagerFactory;

    private LogDao logDao;

    @Override
    public void run() {
        // Very simplified class
        EntityManager entityManager = null;
        try {
            entityManager = this.entityManagerFactory.createEntityManager();
            ...
            this.logDao.insertLog("Test message", CATEGORY);
            ...
            this.getCountry(entityManager, "country")
        } finally {
            entityManager.close();
        }
    }

    private synchronized Country getCountry(final EntityManager entityManager, String strCountry) {
        Country country = Country.getCountryByCountry(entityManager, strCountry);

        if (country == null) {
            country = ImdbToMovieDatabase.convert(strCountry, Country.class);

            entityManager.getTransaction().begin();
            entityManager.persist(country);
            entityManager.getTransaction().commit();

            country = Country.getCountryByCountry(entityManager, strCountry);
        }
        return country;
    }

    public void setLogDao(final LogDao logDao) {
        this.logDao = logDao;
    }
}

以及引起死锁的类:

日志道.java:

public class LogDao {

    private EntityManagerFactory entityManagerFactory;

    public void insertLog(final String message, final Category.CategoryName categoryName) {
        Log log = new Log();
        EntityManager entityManager = this.entityManagerFactory.createEntityManager();

        try {
            entityManager.getTransaction().begin();

            Category category = Category.getCategoryByName(entityManager, categoryName.name());

            log.setCategory(category);
            log.setLog(message);

            entityManager.persist(log); // // Cause of deadlock in Thread-1 (for example)
            entityManager.getTransaction().commit();
        } catch (Exception e) {
            logger.error("Error persisting log [" + message + "]", e);
            entityManager.getTransaction().rollback();
        } finally {
            if (entityManager != null) {
                entityManager.close();
            }
        }
    }

    public void setEntityManagerFactory(final EntityManagerFactory entityManagerFactory) {
        this.entityManagerFactory = entityManagerFactory;
    }
}

实体.java:

public static Country getCountryByCountry(final EntityManager entityManager, final String strCountry) {
    Country country = null;

    try {
        TypedQuery<Country> typedQuery = 
                entityManager.createQuery(
                        "SELECT c FROM Country c WHERE country = ?", 
                        Country.class);

        typedQuery.setParameter(1, strCountry);
        country = typedQuery.getSingleResult(); // Cause of deadlock in Thread-2 (for example)
    } catch (NoResultException e) {
        System.out.println("No data found for " + strCountry);
    } catch (Exception e) {
        e.printStackTrace();
    }

    return country;
}

也许private synchronized Country getCountry是在挑起僵局?我认为我不应该,因为 logDao 已经注入了自己的 EntityManagerFactory

我可以使用连接池吗?

提前致谢。

标签: javadependency-injectionentitymanager

解决方案


推荐阅读