web-dev-qa-db-ja.com

Spring管理のトランザクションEntityManagerでカスタムSQLクエリを実行する方法

Springで構築されたアプリケーションがあります。 Javaオブジェクトにマップされているエンティティを操作している限り、Springに@Transactionalのすべてのマジックを実行させ、すべてが正常に機能します。

しかし、Javaエンティティのいずれにもマップされていないテーブルでカスタムジョブを実行したい場合は、行き詰まっています。少し前に、次のようなカスタムクエリを実行するソリューションを見つけました。

// em is instance of EntityManager
em.getTransaction().begin();
Statement st = em.unwrap(Connection.class).createStatement();
ResultSet rs = st.executeQuery("SELECT custom FROM my_data");
em.getTransaction().commit();

@PersistenceContextアノテーションを付けてSpringから挿入されたエンティティマネージャーでこれを試すと、ほぼ明らかな例外が発生します。

Java.lang.IllegalStateException: 
Not allowed to create transaction on shared EntityManager - 
use Spring transactions or EJB CMT instead

ようやく、次のようにして非共有エンティティーマネージャーを抽出することができました。

@Inject
public void myCustomSqlExecutor(EntityManagerFactory emf){
    EntityManager em = emf.createEntityManager();
    // the em.unwrap(...) stuff from above works fine here
}

それにもかかわらず、このソリューションは快適でもエレガントでもありません。このSpring-transactional-driven環境でカスタムSQLクエリを実行する他の方法があるのだろうか?

気になる人のために-この問題は、アプリケーションと関連フォーラムで一度にユーザーアカウントを作成しようとしたときに発生しました-フォーラムのユーザーテーブルをJavaエンティティのいずれにもマッピングしたくない。

12
fracz

createNativeQuery を使用して、データベースで任意のSQLを実行できます。

EntityManager em = emf.createEntityManager();
List<Object> results = em.createNativeQuery("SELECT custom FROM my_data").getResultList();

上記の答えはまだ当てはまりますが、この質問を見ている人にも関連する可能性があるいくつかの追加情報を編集したいと思います。

createNativeQuery メソッドを使用してEntityManagerを介してネイティブクエリを実行できることは事実です。 Spring Frameworkを使用している場合は、別の(おそらくより良い)方法があります。

Springでクエリを実行する(構成されたトランザクションで動作する)代替の方法は、 JDBCTemplate を使用することです。同じアプリケーション内で、JDBCTemplateJPA EntityManagerの両方を使用することが可能です。構成は次のようになります。

InfrastructureConfig.class:

@Configuration
@Import(AppConfig.class)
public class InfrastructureConfig {

    @Bean //Creates an in-memory database.
    public DataSource dataSource(){
        return new EmbeddedDatabaseBuilder().build(); 
    }   

    @Bean //Creates our EntityManagerFactory
    public AbstractEntityManagerFactoryBean entityManagerFactory(DataSource dataSource){
        LocalContainerEntityManagerFactoryBean emf = new LocalContainerEntityManagerFactoryBean();
        emf.setDataSource(dataSource);
        emf.setJpaVendorAdapter(new HibernateJpaVendorAdapter());

        return emf;
    }

    @Bean //Creates our PlatformTransactionManager. Registering both the EntityManagerFactory and the DataSource to be shared by the EMF and JDBCTemplate
    public PlatformTransactionManager transactionManager(EntityManagerFactory emf, DataSource dataSource){
        JpaTransactionManager tm = new JpaTransactionManager(emf);
        tm.setDataSource(dataSource);
        return tm;
    }

}

AppConfig.class:

@Configuration
@EnableTransactionManagement
public class AppConfig {

    @Bean
    public MyService myTransactionalService(DomainRepository domainRepository) {
        return new MyServiceImpl(domainRepository);
    }

    @Bean
    public DomainRepository domainRepository(JdbcTemplate template){
        return new JpaAndJdbcDomainRepository(template);
    }

    @Bean
    public JdbcTemplate jdbcTemplate(DataSource dataSource){
        JdbcTemplate template = new JdbcTemplate(dataSource);
        return template;
    }
}

JPAとJDBCの両方を使用するリポジトリの例:

public class JpaAndJdbcDomainRepository implements DomainRepository{

    private JdbcTemplate template;
    private EntityManager entityManager;

    //Inject the JdbcTemplate (or the DataSource and construct a new JdbcTemplate)
    public DomainRepository(JdbcTemplate template){
        this.template = template;
    }

    //Inject the EntityManager
    @PersistenceContext
    void setEntityManager(EntityManager entityManager) {
        this.entityManager = entityManager;
    }

    //Execute a JPA query
    public DomainObject getDomainObject(Long id){
        return entityManager.find(id);
    }

    //Execute a native SQL Query
    public List<Map<String,Object>> getData(){
        return template.queryForList("select custom from my_data");
    }
}
11
FGreg

EntityManager.createNativeQuery(String sql) を使用して直接SQLコードを使用するか、 EntityManager.createNamedQuery(String name) を使用してプリコンパイル済みクエリを実行できます。引き続きスプリング管理のエンティティマネージャーを使用しているが、非管理対象オブジェクトで作業している

2