Как выполнить пользовательский SQL-запрос с помощью транзакционного EntityManager, управляемого пружиной

У меня есть приложение, построенное на Spring. Я позволяю Spring делать всю магию @Transactional, и все работает нормально, пока я работаю со своими сущностями, которые сопоставлены с объектами Java.

Однако, когда я хочу выполнить какую-то пользовательскую работу над таблицей, которая не сопоставлена ​​ни с одной из моих сущностей 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 Manager следующим образом:

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

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

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


person fracz    schedule 15.08.2013    source источник
comment
Это непонятно, что вы путаете СМТ и СМТ в одном и том же месте.   -  person Roman C    schedule 16.08.2013


Ответы (2)


Вы можете использовать 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. Конфигурация будет выглядеть примерно так:

ИнфраструктураConfig.класс:

@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.класс:

@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");
    }
}
person FGreg    schedule 15.08.2013
comment
Большой! Я попробовал метод createQuery(), но с тем же исключением, что и выше. createNativeQuery() решает проблему, большое спасибо! - person fracz; 16.08.2013

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

person Luca Basso Ricci    schedule 15.08.2013