Как выполнить пользовательский 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, но работаете с неуправляемыми объектами