2013-08-15 6 views
9

У меня есть приложение, построенное на Spring. Я позволяю Spring делать все волшебство @Transactional, и все работает отлично, пока я работаю над объектами, которые отображаются на объекты Java.Как выполнить пользовательский SQL-запрос с управляемой транзакцией транзакций EntityManager

Однако, когда я хочу выполнить какое-то пользовательское задание в таблице, которая не сопоставлена ​​ни с одним из моих объектов 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(); 

Когда я пытаюсь это с менеджером объекта, впрыскиваемого из Spring с @PersistenceContext аннотацию, я получаю почти очевидное исключение:

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

я, наконец, удалось извлечь неразделяемую Entity менеджер вроде этого:

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

тем не менее, я считаю, это решение ни комфортный, ни электронной legant. Мне просто интересно, есть ли какой-либо другой способ запуска пользовательских SQL-запросов в этой среде с Spring-транзакцией?

Для тех, кому это любопытно, эта проблема возникла, когда я попытался создать учетные записи пользователей в своем приложении и в соответствующем форуме сразу - я не хотел, чтобы таблица пользователей форума отображалась на любом из моих объектов Java.

+0

Непонятно, что вы возили CMT и BMT в одном и том же месте. –

ответ

8

Вы можете использовать createNativeQuery для выполнения любого произвольного SQL в вашей базе данных.

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

выше ответ все еще сохраняется, но я хотел бы изменить в некоторой дополнительной информации, которая может иметь отношение к людям, глядя на этот вопрос.

Хотя верно, что вы можете использовать метод createNativeQuery для выполнения собственных запросов через EntityManager; есть альтернативный (возможно лучший) способ сделать это, если вы используете Spring Framework.

Альтернативный способ выполнения запросов с Spring (который будет вести себя с использованием сконфигурированных транзакций) - использовать JDBCTemplate. В одном приложении можно использовать как JDBCTemplate , так и JPA 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"); 
    } 
} 
+0

Отлично! Я попробовал метод 'createQuery()', но с тем же исключением, как описано выше. 'createNativeQuery()' решает проблему, большое спасибо! – fracz

2

Вы можете использовать EntityManager.createNativeQuery(String sql), чтобы использовать код прямой sql или использовать EntityManager.createNamedQuery(String name) для выполнения предварительно скомпилированного запроса. Вы по-прежнему используете управляемый сущностью под управлением Spring, но работаете над не управляемыми объектами.