I have a simple Dog entity:
Dog.java:
@Entity
@Table(name = "dogs")
public class Dog {
public Dog() {}
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column
private String name;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
DogController.java:
@RestController
public class DogController {
@Autowired
private DogRepository dogRepository;
@RequestMapping("/api/dogs/insert/default")
@Transactional
public String insertDefault() {
Dog dog = new Dog();
dog.setName("Alpha dog");
dogRepository.save(dog);
return "dog inserted OK";
}
}
DogRepository:
@Repository
public interface DogRepository extends JpaRepository<Dog, Long> {
}
However when I go to /api/dogs/insert/default it simply displays dog inserted OK without changing a database or even attempting to change it (no queries are printed to the console).
What could be the problem here? In debugger I can see that dogRepository is not null.
My WebMvcConfigurer class looks like this:
@EnableWebMvc
@Configuration
@EnableJpaRepositories("testproject")
@EnableTransactionManagement
@ComponentScan("testproject")
public class WebConfig implements WebMvcConfigurer {...}
I tried adding the @EnableTransactionManagement to it, but it didn't work
EDIT:
I don't have an application.properties file. All properties are configured using Java classes.
DbConfig.java:
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(basePackages = "testproject", entityManagerFactoryRef = "entityManagerFactory")
public class DbConfig {
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
entityManagerFactoryBean.setDataSource(getDatasource());
entityManagerFactoryBean.setPersistenceProviderClass(HibernatePersistenceProvider.class);
entityManagerFactoryBean.setJpaProperties(getHibernateProperties());
entityManagerFactoryBean.setPackagesToScan("testproject");
return entityManagerFactoryBean;
}
@Bean
public DataSource getDatasource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("org.postgresql.Driver");
dataSource.setUrl("jdbc:postgresql://127.0.0.1:5432/testproject");
dataSource.setUsername("postgres");
dataSource.setPassword("bigfalcon");
return dataSource;
}
@Bean
public SessionFactory getSessionFactory() throws IOException {
LocalSessionFactoryBean sessionFactoryBean = new LocalSessionFactoryBean();
sessionFactoryBean.setPackagesToScan("testproject");
//getHibernateProperties method is a private method
sessionFactoryBean.setHibernateProperties(getHibernateProperties());
sessionFactoryBean.setDataSource(getDatasource());
sessionFactoryBean.afterPropertiesSet();
return sessionFactoryBean.getObject();
}
@Bean(name = "transactionManager")
public HibernateTransactionManager getTransactionManager() throws IOException {
HibernateTransactionManager transactionManager = new HibernateTransactionManager();
transactionManager.setSessionFactory(getSessionFactory());
return transactionManager;
}
@Bean
private static Properties getHibernateProperties() {
Properties hibernateProperties = new Properties(); //PostgreSQLDialect
//hibernateProperties.put("hibernate.dialect", "org.hibernate.dialect.HSQLDialect");
hibernateProperties.put("hibernate.dialect", "org.hibernate.dialect.PostgreSQLDialect");
hibernateProperties.put("hibernate.show_sql", true);
// hibernateProperties.put("spring.jpa.hibernate.ddl-auto", "create");
hibernateProperties.put( "hibernate.hbm2ddl.auto", "create-drop");
/*hibernateProperties.setProperty(
"hibernate.dialect", "org.hibernate.dialect.H2Dialect");
*/
System.out.println();
// other properties
return hibernateProperties;
}
}
EDIT:
Using the same dogRepository to select existing dogs from the database (which I manually inserted) works fine:
@RequestMapping("/api/dogs/view/all")
public Iterable<Dog> viewDogs() {
Iterable<Dog> dogs = dogRepository.findAll(); //non-null size, returns a JSON with dogs
return dogs;
}
EDIT: Maven dependencies:
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-core -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
<version>5.3.1.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-web -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-core -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
<version>5.3.1.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-config -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>5.3.1.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-web -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>5.3.1.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.data/spring-data-jpa -->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<version>2.2.6.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-core -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf</artifactId>
<version>3.0.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring4</artifactId>
<version>3.0.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.apache.opennlp</groupId>
<artifactId>opennlp-tools</artifactId>
<version>1.9.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/net.dv8tion/JDA -->
<dependency>
<groupId>net.dv8tion</groupId>
<artifactId>JDA</artifactId>
<version>4.0.0_46</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.hibernate/hibernate-core -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>5.4.4.Final</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.h2database/h2 -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.197</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-orm -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>9.4-1203-jdbc4</version>
</dependency>
<!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.security.oauth/spring-security-oauth2 -->
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.4.1.RELEASE</version>
</dependency>
<!--is needed for jwt-->
<!-- https://mvnrepository.com/artifact/javax.xml/jaxb-api -->
<dependency>
<groupId>javax.xml</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.sun.xml.bind/jaxb-impl -->
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<version>3.0.0-M4</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.sun.xml.bind/jaxb-core -->
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-core</artifactId>
<version>3.0.0-M4</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-jwt -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-jwt</artifactId>
<version>1.1.0.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.jakewharton.fliptables/fliptables -->
<dependency>
<groupId>com.jakewharton.fliptables</groupId>
<artifactId>fliptables</artifactId>
<version>1.0.2</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>3.4.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.json/json -->
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20190722</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.8</version>
</dependency>
</dependencies>
EDIT:
I also tried to get it to update the entity:
@RequestMapping("/api/dogs/update/{id}")
public Iterable<Dog> updateDog(@PathVariable("id") long id) {
Optional<Dog> dog = dogRepository.findById(id);
Dog dogValue = dog.get();
dogValue.setName("New name");
dogRepository.save(dogValue);
return dogRepository.findAll();
}
which however doesn't work: the JSON returned to the browser contains updated name (New name), but doesn't update the database itself.
EDIT:
I managed to get it to work with manual Hibernate commands, see the added methods insertDog(Dog dog) and updateDog(Dog dog): I take the Session object from autowired DbConfig object and use session.save(dog), session.flush() to update the database. With the following controller code both /api/dogs/insert/default and /api/dogs/update/3/Buddy work fine.
So the problem seems to be related to JpaRepository specifically, seeing as if I forego using it and write the Hibernate code myself, it works fine:
DogController.java:
@CrossOrigin
@RestController
@Transactional( readOnly = false)
public class DogController {
@Autowired
private DogRepository dogRepository;
@Autowired
private DbConfig dbConfig;
@RequestMapping("/api/dogs/insert/default")
@Transactional(readOnly=false)
public String insertDefault() throws Exception {
Dog dog = new Dog();
dog.setName("Alpha dog");
//dogRepository.save(dog);
insertDog(dog);
return "dog inserted OK";
}
@RequestMapping("/api/dogs/view/all")
public Iterable<Dog> viewDogs() {
Iterable<Dog> dogs = dogRepository.findAll();
return dogs;
}
@RequestMapping("/api/dogs/update/{id}/{name}")
public Iterable<Dog> updateDog(@PathVariable("id") long id,
@PathVariable("name") String name) throws Exception {
Optional<Dog> dog = dogRepository.findById(id);
Dog dogValue = dog.get();
dogValue.setName(name);
//dogRepository.save(dogValue);
Session session = dbConfig.getSessionFactory().openSession();
updateDog(dogValue);
return dogRepository.findAll();
}
public void updateDog(Dog dog) throws Exception {
Session session = dbConfig.getSessionFactory().openSession();
session.beginTransaction();
session.merge(dog);
session.flush();
session.getTransaction().commit();
session.clear();
session.close();
}
public boolean insertDog(Dog dog) throws Exception {
boolean result = false;
Session session = dbConfig.getSessionFactory().openSession();
session.beginTransaction();
try {
session.save(dog);
session.flush();
session.getTransaction().commit();
result = true;
}
catch (Exception e) {
result = false;
}
finally {
session.clear();
session.close();
}
return result;
}
}
EDIT:
I wonder if something to do with generating ids of the Dog entity could be the problem. I use PostgreSQL and in the logs I see
WARN [RMI TCP Connection(2)-127.0.0.1] org.hibernate.engine.jdbc.spi.SqlExceptionHelper$StandardWarningHandler.logWarning sequence "hibernate_sequence" does not exist, skipping
And then when I access /api/dogs/insert/default in console
Hibernate: select nextval ('hibernate_sequence')
gets printed.
I tried changing GenerationStrategy but it didn't help. I also tried adding
hibernateProperties.put("hibernate.connection.driver_class", "org.postgresql.Driver");
to getHibernateProperties() method in DbConfig.java but it didn't seem to help either.
EDIT:
I also tried changing HibernateTransactionManager to JpaTransactionManager in transactionManager() method in DbConfig.java:
@Bean(name = "transactionManager")
public JpaTransactionManager transactionManager() throws IOException {
JpaTransactionManager transactionManager = new JpaTransactionManager();
//transactionManager.setSessionFactory(getSessionFactory());
return transactionManager;
}
but that resulted the
NoUniqueBeanDefinitionException: No qualifying bean of type 'javax.persistence.EntityManagerFactory' available: more than one 'primary' bean found among candidates: [entityManagerFactory, getSessionFactory]
error on server startup
I also figured that maybe JpaRepository can't generate the id, so I tried making it manual:
@Id
@Column(name = "dog_id", unique = true, nullable = false)
private Long id;
and in DogController.java:
@RequestMapping("/api/dogs/insert/default")
@Transactional(readOnly=false, propagation = Propagation.REQUIRED)
public String insertDefault() throws Exception {
Dog dog = new Dog();
Random random = new Random();
dog.setId((long) random.nextInt(1000_000_000));
dog.setName("Alpha dog");
dogRepository.save(dog);
dogRepository.flush();
//insertDog(dog);
return "dog inserted OK";
}
however it resulted in error:
org.springframework.web.util.NestedServletException:
Request processing failed; nested exception is org.springframework.dao.InvalidDataAccessApiUsageException:
no transaction is in progress; nested exception is javax.persistence.TransactionRequiredException: no transaction is in progress
EDIT:
Also I figured out why there was no no transaction is in progress error earlier (that error helped understand what was wrong): the error only appeared after I added
dogRepository.flush();
to DogController.java.
If I remove dogRepository.flush() the dogRepository.save(dog) method still wouldn't work, but no visible error would appear (other than Hibernate: select nextval ('hibernate_sequence') in console).
I managed to make JpaRepository insert on /api/dogs/insert/default work!
What it took: adding
hibernateProperties.put("hibernate.allow_update_outside_transaction", true);
to getHibernateProperties() method of DbConfig.java class.
However I'm not sure that's a correct way to handle it? And if there are better methods of dealing with that error? And why that error only appeared when I use JpaRepository for inserting/updating entities, while if I use raw Hibernate commands (session.save()) the error didn't appear and updates worked fine?