简体   繁体   English

Spring 数据 JPA 更新@查询不更新?

[英]Spring Data JPA Update @Query not updating?

I have an update query:我有一个更新查询:

@Modifying
@Transactional
@Query("UPDATE Admin SET firstname = :firstname, lastname = :lastname, login = :login, superAdmin = :superAdmin, preferenceAdmin = :preferenceAdmin, address =  :address, zipCode = :zipCode, city = :city, country = :country, email = :email, profile = :profile, postLoginUrl = :postLoginUrl WHERE id = :id")
public void update(@Param("firstname") String firstname, @Param("lastname") String lastname, @Param("login") String login, @Param("superAdmin") boolean superAdmin, @Param("preferenceAdmin") boolean preferenceAdmin, @Param("address") String address, @Param("zipCode") String zipCode, @Param("city") String city, @Param("country") String country, @Param("email") String email, @Param("profile") String profile, @Param("postLoginUrl") String postLoginUrl, @Param("id") Long id);

I'm trying to use it in an integration test:我正在尝试在集成测试中使用它:

adminRepository.update("Toto", "LeHeros", admin0.getLogin(), admin0.getSuperAdmin(), admin0.getPreferenceAdmin(), admin0.getAddress(), admin0.getZipCode(), admin0.getCity(), admin0.getCountry(), admin0.getEmail(), admin0.getProfile(), admin0.getPostLoginUrl(), admin0.getId());
Admin loadedAdmin = adminRepository.findOne(admin0.getId());
assertEquals("Toto", loadedAdmin.getFirstname());
assertEquals("LeHeros", loadedAdmin.getLastname());

But the fields are not updated and retain their initial values, the test thus failing.但是这些字段没有更新并保留它们的初始值,因此测试失败。

I tried adding a flush right before the findOne query:我尝试在 findOne 查询之前添加一个刷新:

adminRepository.flush();

But the failed assertion remained identical.但是失败的断言仍然是相同的。

I can see the update sql statement in the log:我可以在日志中看到更新 sql 语句:

update admin set firstname='Toto', lastname='LeHeros', login='stephane', super_admin=0, preference_admin=0,
address=NULL, zip_code=NULL, city=NULL, country=NULL, email='stephane@thalasoft.com', profile=NULL,
post_login_url=NULL where id=2839

But the log shows no sql that could relate to the finder:但是日志显示没有可能与查找器相关的 sql:

Admin loadedAdmin = adminRepository.findOne(admin0.getId());
The finder sql statement is not making its way to the database.

Is it ignored for some caching reason?是否由于某些缓存原因而被忽略?

If I then add a call to the findByEmail and findByLogin finders as in:如果我随后添加对 findByEmail 和 findByLogin 查找器的调用,如下所示:

adminRepository.update("Toto", "LeHeros", "qwerty", admin0.getSuperAdmin(), admin0.getPreferenceAdmin(), admin0.getAddress(), admin0.getZipCode(), admin0.getCity(), admin0.getCountry(), admin0.getEmail(), admin0.getProfile(), admin0.getPostLoginUrl(), admin0.getId());
Admin loadedAdmin = adminRepository.findOne(admin0.getId());
Admin myadmin = adminRepository.findByEmail(admin0.getEmail());
Admin anadmin = adminRepository.findByLogin("qwerty");
assertEquals("Toto", anadmin.getFirstname());
assertEquals("Toto", myadmin.getFirstname());
assertEquals("Toto", loadedAdmin.getFirstname());
assertEquals("LeHeros", loadedAdmin.getLastname());

then I can see in the log the sql statement being generated:然后我可以在日志中看到正在生成的 sql 语句:

But the assertion:但断言:

assertEquals("Toto", myadmin.getFirstname());

still fails even though the trace shows the same domain object was retrieved:即使跟踪显示检索到相同的域 object 仍然失败:

TRACE [BasicExtractor] found [1037] as column [id14_]

One other thing that puzzles me with this other finder is that it shows a limit 2 clause even though it is supposed to return only one Admin object.另一个让我困惑的另一件事是它显示了一个限制 2 子句,即使它应该只返回一个管理员 object。

I thought there would always be a limit 1 when returning one domain object.我认为返回一个域 object 时总会有一个限制 1。 Is this a wrong assumption on Spring Data?这是对 Spring 数据的错误假设吗?

When pasting in a MySQL client, the sql statements displayed in the console log, the logic works fine:在 MySQL 客户端中粘贴时,控制台日志中显示 sql 语句,逻辑工作正常:

mysql> insert into admin (version, address, city, country, email, firstname, lastname, login, password, 
-> password_salt, post_login_url, preference_admin, profile, super_admin, zip_code) values (0,
-> NULL, NULL, NULL, 'zemail@thalasoft.com039', 'zfirstname039', 'zlastname039', 'zlogin039',
-> 'zpassword039', '', NULL, 0, NULL, 1, NULL);
Query OK, 1 row affected (0.07 sec)

mysql> select * from admin;
+------+---------+---------------+--------------+-----------+--------------+---------------+-------------+------------------+---------+----------+------+---------+-------------------------+---------+----------------+
| id | version | firstname | lastname | login | password | password_salt | super_admin | preference_admin | address | zip_code | city | country | email | profile | post_login_url |
+------+---------+---------------+--------------+-----------+--------------+---------------+-------------+------------------+---------+----------+------+---------+-------------------------+---------+----------------+
| 1807 | 0 | zfirstname039 | zlastname039 | zlogin039 | zpassword039 | | 1 | 0 | NULL | NULL | NULL | NULL | zemail@thalasoft.com039 | NULL | NULL | 
+------+---------+---------------+--------------+-----------+--------------+---------------+-------------+------------------+---------+----------+------+---------+-------------------------+---------+----------------+
1 row in set (0.00 sec)

mysql> update admin set firstname='Toto', lastname='LeHeros', login='qwerty', super_admin=0, preference_admin=0, address=NULL, zip_code=NULL, city=NULL, country=NULL, email='stephane@thalasoft.com', profile=NULL, post_login_url=NULL where id=1807;
Query OK, 1 row affected (0.07 sec)
Rows matched: 1 Changed: 1 Warnings: 0

mysql> select * from admin; +------+---------+-----------+----------+--------+--------------+---------------+-------------+------------------+---------+----------+------+---------+------------------------+---------+----------------+
| id | version | firstname | lastname | login | password | password_salt | super_admin | preference_admin | address | zip_code | city | country | email | profile | post_login_url |
+------+---------+-----------+----------+--------+--------------+---------------+-------------+------------------+---------+----------+------+---------+------------------------+---------+----------------+
| 1807 | 0 | Toto | LeHeros | qwerty | zpassword039 | | 0 | 0 | NULL | NULL | NULL | NULL | stephane@thalasoft.com | NULL | NULL | 
+------+---------+-----------+----------+--------+--------------+---------------+-------------+------------------+---------+----------+------+---------+------------------------+---------+----------------+
1 row in set (0.00 sec)

mysql> select admin0_.id as id14_, admin0_.version as version14_, admin0_.address as address14_, admin0_.city as city14_, admin0_.country as country14_, admin0_.email as email14_, admin0_.firstname as firstname14_, admin0_.lastname as lastname14_, admin0_.login as login14_, admin0_.password as password14_, admin0_.password_salt as password11_14_, admin0_.post_login_url as post12_14_, admin0_.preference_admin as preference13_14_, admin0_.profile as profile14_, admin0_.super_admin as super15_14_, admin0_.zip_code as zip16_14_ from admin admin0_ where admin0_.email='stephane@thalasoft.com' limit 2;
+-------+------------+------------+---------+------------+------------------------+--------------+-------------+----------+--------------+----------------+------------+------------------+------------+-------------+-----------+
| id14_ | version14_ | address14_ | city14_ | country14_ | email14_ | firstname14_ | lastname14_ | login14_ | password14_ | password11_14_ | post12_14_ | preference13_14_ | profile14_ | super15_14_ | zip16_14_ |
+-------+------------+------------+---------+------------+------------------------+--------------+-------------+----------+--------------+----------------+------------+------------------+------------+-------------+-----------+
| 1807 | 0 | NULL | NULL | NULL | stephane@thalasoft.com | Toto | LeHeros | qwerty | zpassword039 | | NULL | 0 | NULL | 0 | NULL | 
+-------+------------+------------+---------+------------+------------------------+--------------+-------------+----------+--------------+----------------+------------+------------------+------------+-------------+-----------+
1 row in set (0.00 sec)

mysql> select admin0_.id as id14_, admin0_.version as version14_, admin0_.address as address14_, admin0_.city as city14_, admin0_.country as country14_, admin0_.email as email14_, admin0_.firstname as firstname14_, admin0_.lastname as lastname14_, admin0_.login as login14_, admin0_.password as password14_, admin0_.password_salt as password11_14_, admin0_.post_login_url as post12_14_, admin0_.preference_admin as preference13_14_, admin0_.profile as profile14_, admin0_.super_admin as super15_14_, admin0_.zip_code as zip16_14_ from admin admin0_ where admin0_.login='qwerty' limit 2;
+-------+------------+------------+---------+------------+------------------------+--------------+-------------+----------+--------------+----------------+------------+------------------+------------+-------------+-----------+
| id14_ | version14_ | address14_ | city14_ | country14_ | email14_ | firstname14_ | lastname14_ | login14_ | password14_ | password11_14_ | post12_14_ | preference13_14_ | profile14_ | super15_14_ | zip16_14_ |
+-------+------------+------------+---------+------------+------------------------+--------------+-------------+----------+--------------+----------------+------------+------------------+------------+-------------+-----------+
| 1807 | 0 | NULL | NULL | NULL | stephane@thalasoft.com | Toto | LeHeros | qwerty | zpassword039 | | NULL | 0 | NULL | 0 | NULL | 
+-------+------------+------------+---------+------------+------------------------+--------------+-------------+----------+--------------+----------------+------------+------------------+------------+-------------+-----------+
1 row in set (0.00 sec)

So why is this not reflected at the Java level?那么为什么这在 Java 层面没有体现出来呢?

The EntityManager doesn't flush change automatically by default.默认情况下,EntityManager 不会自动刷新更改。 You should use the following option with your statement of query:您应该在查询语句中使用以下选项:

@Modifying(clearAutomatically = true)
@Query("update RssFeedEntry feedEntry set feedEntry.read =:isRead where feedEntry.id =:entryId")
void markEntryAsRead(@Param("entryId") Long rssFeedEntryId, @Param("isRead") boolean isRead);

I finally understood what was going on.我终于明白是怎么回事了。

When creating an integration test on a statement saving an object, it is recommended to flush the entity manager so as to avoid any false negative, that is, to avoid a test running fine but whose operation would fail when run in production.在对保存对象的语句创建集成测试时,建议刷新实体管理器以避免任何假阴性,即避免测试运行良好但在生产中运行时其操作会失败。 Indeed, the test may run fine simply because the first level cache is not flushed and no writing hits the database.事实上,测试可能运行良好,因为第一级缓存没有被刷新并且没有写入命中数据库。 To avoid this false negative integration test use an explicit flush in the test body.为了避免这种假阴性集成测试,在测试主体中使用显式刷新。 Note that the production code should never need to use any explicit flush as it is the role of the ORM to decide when to flush.请注意,生产代码永远不需要使用任何显式刷新,因为 ORM 的作用是决定何时刷新。

When creating an integration test on an update statement, it may be necessary to clear the entity manager so as to reload the first level cache.在更新语句上创建集成测试时,可能需要清除实体管理器以重新加载一级缓存。 Indeed, an update statement completely bypasses the first level cache and writes directly to the database.事实上,一条更新语句完全绕过一级缓存,直接写入数据库。 The first level cache is then out of sync and reflects the old value of the updated object.第一级缓存然后不同步并反映更新对象的旧值。 To avoid this stale state of the object, use an explicit clear in the test body.要避免对象的这种陈旧状态,请在测试正文中使用明确的 clear。 Note that the production code should never need to use any explicit clear as it is the role of the ORM to decide when to clear.请注意,生产代码永远不需要使用任何显式清除,因为决定何时清除是 ORM 的角色。

My test now works just fine.我的测试现在工作正常。

I was able to get this to work.我能够让这个工作。 I will describe my application and the integration test here.我将在这里描述我的应用程序和集成测试。

The Example Application示例应用程序

The example application has two classes and one interface that are relevant to this problem:示例应用程序具有与此问题相关的两个类和一个接口:

  1. The application context configuration class应用上下文配置类
  2. The entity class实体类
  3. The repository interface存储库界面

These classes and the repository interface are described in the following.这些类和存储库接口在下面描述。

The source code of the PersistenceContext class looks as follows: PersistenceContext类的源代码如下所示:

import com.jolbox.bonecp.BoneCPDataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.sql.DataSource;
import java.util.Properties;

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(basePackages = "net.petrikainulainen.spring.datajpa.todo.repository")
@PropertySource("classpath:application.properties")
public class PersistenceContext {

    protected static final String PROPERTY_NAME_DATABASE_DRIVER = "db.driver";
    protected static final String PROPERTY_NAME_DATABASE_PASSWORD = "db.password";
    protected static final String PROPERTY_NAME_DATABASE_URL = "db.url";
    protected static final String PROPERTY_NAME_DATABASE_USERNAME = "db.username";

    private static final String PROPERTY_NAME_HIBERNATE_DIALECT = "hibernate.dialect";
    private static final String PROPERTY_NAME_HIBERNATE_FORMAT_SQL = "hibernate.format_sql";
    private static final String PROPERTY_NAME_HIBERNATE_HBM2DDL_AUTO = "hibernate.hbm2ddl.auto";
    private static final String PROPERTY_NAME_HIBERNATE_NAMING_STRATEGY = "hibernate.ejb.naming_strategy";
    private static final String PROPERTY_NAME_HIBERNATE_SHOW_SQL = "hibernate.show_sql";

    private static final String PROPERTY_PACKAGES_TO_SCAN = "net.petrikainulainen.spring.datajpa.todo.model";

    @Autowired
    private Environment environment;

    @Bean
    public DataSource dataSource() {
        BoneCPDataSource dataSource = new BoneCPDataSource();

        dataSource.setDriverClass(environment.getRequiredProperty(PROPERTY_NAME_DATABASE_DRIVER));
        dataSource.setJdbcUrl(environment.getRequiredProperty(PROPERTY_NAME_DATABASE_URL));
        dataSource.setUsername(environment.getRequiredProperty(PROPERTY_NAME_DATABASE_USERNAME));
        dataSource.setPassword(environment.getRequiredProperty(PROPERTY_NAME_DATABASE_PASSWORD));

        return dataSource;
    }

    @Bean
    public JpaTransactionManager transactionManager() {
        JpaTransactionManager transactionManager = new JpaTransactionManager();

        transactionManager.setEntityManagerFactory(entityManagerFactory().getObject());

        return transactionManager;
    }

    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
        LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();

        entityManagerFactoryBean.setDataSource(dataSource());
        entityManagerFactoryBean.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
        entityManagerFactoryBean.setPackagesToScan(PROPERTY_PACKAGES_TO_SCAN);

        Properties jpaProperties = new Properties();
        jpaProperties.put(PROPERTY_NAME_HIBERNATE_DIALECT, environment.getRequiredProperty(PROPERTY_NAME_HIBERNATE_DIALECT));
        jpaProperties.put(PROPERTY_NAME_HIBERNATE_FORMAT_SQL, environment.getRequiredProperty(PROPERTY_NAME_HIBERNATE_FORMAT_SQL));
        jpaProperties.put(PROPERTY_NAME_HIBERNATE_HBM2DDL_AUTO, environment.getRequiredProperty(PROPERTY_NAME_HIBERNATE_HBM2DDL_AUTO));
        jpaProperties.put(PROPERTY_NAME_HIBERNATE_NAMING_STRATEGY, environment.getRequiredProperty(PROPERTY_NAME_HIBERNATE_NAMING_STRATEGY));
        jpaProperties.put(PROPERTY_NAME_HIBERNATE_SHOW_SQL, environment.getRequiredProperty(PROPERTY_NAME_HIBERNATE_SHOW_SQL));

        entityManagerFactoryBean.setJpaProperties(jpaProperties);

        return entityManagerFactoryBean;
    }
}

Let's assume that we have a simple entity called Todo which source code looks as follows:假设我们有一个名为Todo的简单实体,其源代码如下所示:

@Entity
@Table(name="todos")
public class Todo {

    public static final int MAX_LENGTH_DESCRIPTION = 500;
    public static final int MAX_LENGTH_TITLE = 100;

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Column(name = "description", nullable = true, length = MAX_LENGTH_DESCRIPTION)
    private String description;

    @Column(name = "title", nullable = false, length = MAX_LENGTH_TITLE)
    private String title;

    @Version
    private long version;
}

Our repository interface has a single method called updateTitle() which updates the title of a todo entry.我们的存储库接口有一个名为updateTitle()方法,用于更新待办事项条目的标题。 The source code of the TodoRepository interface looks as follows: TodoRepository接口的源代码如下所示:

import net.petrikainulainen.spring.datajpa.todo.model.Todo;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

import java.util.List;

public interface TodoRepository extends JpaRepository<Todo, Long> {

    @Modifying
    @Query("Update Todo t SET t.title=:title WHERE t.id=:id")
    public void updateTitle(@Param("id") Long id, @Param("title") String title);
}

The updateTitle() method is not annotated with the @Transactional annotation because I think that it is best to use a service layer as a transaction boundary. updateTitle()方法没有使用@Transactional注解,因为我认为最好使用服务层作为事务边界。

The Integration Test集成测试

The Integration Test uses DbUnit, Spring Test and Spring-Test-DBUnit.集成测试使用 DbUnit、Spring Test 和 Spring-Test-DBUnit。 It has three components which are relevant to this problem:它具有与此问题相关的三个组件:

  1. The DbUnit dataset which is used to initialize the database into a known state before the test is executed. DbUnit 数据集,用于在执行测试之前将数据库初始化为已知状态。
  2. The DbUnit dataset which is used to verify that the title of the entity is updated. DbUnit 数据集,用于验证实体的标题是否已更新。
  3. The integration test.集成测试。

These components are described with more details in the following.下面将更详细地描述这些组件。

The name of the DbUnit dataset file which is used to initialize the database to known state is toDoData.xml and its content looks as follows:用于将数据库初始化为已知状态的 DbUnit 数据集文件的名称是toDoData.xml ,其内容如下所示:

<dataset>
    <todos id="1" description="Lorem ipsum" title="Foo" version="0"/>
    <todos id="2" description="Lorem ipsum" title="Bar" version="0"/>
</dataset>

The name of the DbUnit dataset which is used to verify that the title of the todo entry is updated is called toDoData-update.xml and its content looks as follows (for some reason the version of the todo entry was not updated but the title was. Any ideas why?):用于验证 todo 条目标题是否更新的 DbUnit 数据集的名称称为toDoData-update.xml ,其内容如下所示(由于某种原因,todo 条目的版本没有更新,但标题是. 任何想法为什么?):

<dataset>
    <todos id="1" description="Lorem ipsum" title="FooBar" version="0"/>
    <todos id="2" description="Lorem ipsum" title="Bar" version="0"/>
</dataset>

The source code of the actual integration test looks as follows (Remember to annotate the test method with the @Transactional annotation):实际集成测试的源码如下(记得用@Transactional注解注解测试方法):

import com.github.springtestdbunit.DbUnitTestExecutionListener;
import com.github.springtestdbunit.TransactionDbUnitTestExecutionListener;
import com.github.springtestdbunit.annotation.DatabaseSetup;
import com.github.springtestdbunit.annotation.ExpectedDatabase;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
import org.springframework.test.context.support.DirtiesContextTestExecutionListener;
import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
import org.springframework.transaction.annotation.Transactional;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {PersistenceContext.class})
@TestExecutionListeners({ DependencyInjectionTestExecutionListener.class,
        DirtiesContextTestExecutionListener.class,
        TransactionalTestExecutionListener.class,
        DbUnitTestExecutionListener.class })
@DatabaseSetup("todoData.xml")
public class ITTodoRepositoryTest {

    @Autowired
    private TodoRepository repository;

    @Test
    @Transactional
    @ExpectedDatabase("toDoData-update.xml")
    public void updateTitle_ShouldUpdateTitle() {
        repository.updateTitle(1L, "FooBar");
    }
}

After I run the integration test, the test passes and the title of the todo entry is updated.在我运行集成测试后,测试通过并且 todo 条目的标题被更新。 The only problem which I am having is that the version field is not updated.我遇到的唯一问题是版本字段未更新。 Any ideas why?任何想法为什么?

I undestand that this description is a bit vague.我不明白这个描述有点含糊。 If you want to get more information about writing integration tests for Spring Data JPA repositories, you can read my blog post about it .如果您想获得有关为 Spring Data JPA 存储库编写集成测试的更多信息,您可以阅读我的博客文章

I struggled with the same problem where I was trying to execute an update query like the same as you did-我在尝试像您一样执行更新查询时遇到了同样的问题-

@Modifying
@Transactional
@Query(value = "UPDATE SAMPLE_TABLE st SET st.status=:flag WHERE se.referenceNo in :ids")
public int updateStatus(@Param("flag")String flag, @Param("ids")List<String> references);

This will work if you have put @EnableTransactionManagement annotation on the main class.如果您在主类上放置了@EnableTransactionManagement注释,这将起作用。 Spring 3.1 introduces the @EnableTransactionManagement annotation to be used in on @Configuration classes and enable transactional support. Spring 3.1 引入了@EnableTransactionManagement注释,用于@Configuration类并启用事务支持。

The underlying problem here is the 1st level cache of JPA.这里的潜在问题是 JPA 的一级缓存。 From the JPA spec Version 2.2 section 3.1.来自 JPA 规范版本 2.2 第 3.1 节。 emphasise is mine:强调是我的:

An EntityManager instance is associated with a persistence context. EntityManager 实例与持久性上下文相关联。 A persistence context is a set of entity instances in which for any persistent entity identity there is a unique entity instance .持久上下文是一组实体实例,其中对于任何持久实体标识,都有一个唯一的实体实例

This is important because JPA tracks changes to that entity in order to flush them to the database.这很重要,因为 JPA 会跟踪对该实体的更改,以便将它们刷新到数据库中。 As a side effect it also means within a single persistence context an entity gets only loaded once.作为副作用,它还意味着在单个持久性上下文中,实体仅加载一次。 This why reloading the changed entity doesn't have any effect.这就是为什么重新加载更改的实体没有任何效果的原因。

You have a couple of options how to handle this:您有几种选择来处理这个问题:

  1. Evict the entity from the EntityManager .EntityManager驱逐实体。 This may be done by calling EntityManager.detach , annotating the updating method with @Modifying(clearAutomatically = true) which evicts all entities.这可以通过调用EntityManager.detach来完成,使用@Modifying(clearAutomatically = true)注释更新方法,驱逐所有实体。 Make sure changes to these entities get flushed first or you might end up loosing changes.确保首先刷新对这些实体的更改,否则您最终可能会丢失更改。

  2. Use EntityManager.refresh() .使用EntityManager.refresh()

  3. Use a different persistence context to load the entity.使用不同的持久化上下文加载实体。 The easiest way to do this is to do it in a separate transaction.最简单的方法是在单独的事务中进行。 With Spring this can be done by having separate methods annotated with @Transactional on beans called from a bean not annotated with @Transactional .使用 Spring,这可以通过在从未使用@Transactional注释的 bean 调用的 bean 上使用@Transactional注释的单独方法来完成。 Another way is to use a TransactionTemplate which works especially nicely in tests where it makes transaction boundaries very visible.另一种方法是使用TransactionTemplate ,它在使事务边界非常明显的测试中特别有效。

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

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