Как выполнить пользовательский SQL-запрос с помощью spring-managed transactional EntityManager

у меня есть приложение, построенное на весну. Я позволил весне сделать все @Transactional magic и все работает нормально, пока я работаю с моими сущностями, которые сопоставлены с объектами 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();

когда я пытаюсь это сделать с помощью entity manager, введенного из Spring с помощью @PersistenceContext аннотация, я получаю почти очевидное исключение:

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

мне, наконец, удалось извлечь non-shared Entity Manager следующим образом:

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

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

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

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

InfrastructureConfig.класс:

@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");
    }
}

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