Spring + JPA + H2 Strange behaviours of lazy fetch and bidirectional OneToMany - spring

I have been playing for a long time with JPA, in the past through EJBs, now with Spring. I have recently noticed some weird behaviours I can hardly explain.
First the bidirectionnal OneToMany
My bidirectional OneToMany is correctly set with a mappedBy.
#Entity
public class EntityOne {
#Id #GeneratedValue
private int id;
#OneToMany(mappedBy = "one")
private Set<EntityTwo> twos;
...
#Entity
public class EntityTwo {
#Id #GeneratedValue
private int id;
#ManyToOne
private EntityOne one;
...
Then this does not update the database :
#Transactional
public void firstWay(){
EntityOne e1=em.find(EntityOne.class,1);
EntityTwo e2=em.find(EntityTwo.class,1);
e1.getTwos().add(e2);
}
while this does :
#Transactional
public void secondWay(){
EntityOne e1=em.find(EntityOne.class,1);
EntityTwo e2=em.find(EntityTwo.class,1);
e2.setOne(e1);
}
I am quite puzzled...
Then the lazy fetch :
// this is just a tool example...
public void someFindBy() {
EntityOne e1=em.find(EntityOne.class,1);
for (EntityTwo e2:e1.getTwos()) {
System.out.println(e2);
}
}
leads to LazyExceptionError... Shouldn't my "e1" entity remain attached until the end of the method and thus hibernate resolve the fetch (I use the default persistence context ,i.e. Transaction scoped. I did also try to make the method transactional by annotating it with #Transactional but that didn't change anything).
So, well, I could use an Entity Graph or a Join Fetch, but, still, I wonder why it doesn't work as is...
Here is the Spring configuration file :
#Configuration
#ComponentScan(basePackages = {"facade"})
#EnableTransactionManagement
public class ClientWebConfig extends WebMvcConfigurerAdapter {
#Bean
public DataSource dataSource() {
EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder();
EmbeddedDatabase db = builder
.setType(EmbeddedDatabaseType.H2)
.build();
return db;
}
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean em
= new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dataSource());
em.setPackagesToScan(new String[] { "model" });
JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
em.setJpaVendorAdapter(vendorAdapter);
em.setJpaProperties(additionalProperties());
return em;
}
Properties additionalProperties() {
Properties properties = new Properties();
properties.setProperty("hibernate.hbm2ddl.auto", "create-drop");
properties.setProperty(
"hibernate.dialect", "org.hibernate.dialect.H2Dialect");
properties.setProperty("hibernate.hbm2ddl.import_files" ,"insert-data.sql");
return properties;
}
#Bean
public PlatformTransactionManager transactionManager(
EntityManagerFactory emf){
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(emf);
return transactionManager;
}
#Bean
public PersistenceExceptionTranslationPostProcessor exceptionTranslation(){
return new PersistenceExceptionTranslationPostProcessor();
}
}

fix this entity.
cascade, fetch must be set to update.
#Entity
public class EntityOne {
#Id #GeneratedValue
private int id;
**#OneToMany(mappedBy = "one", cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)**
private Set<EntityTwo> twos;

Related

Is there a way to stop Spring from rollbacking every transaction in a test method and do a Rollback after it executes?

I'm trying to write some integration tests for an app that I write as a practise. I have a test class with #Transactional annotation on it, to stop tests from committing any changes to db. I've noticed that rollback happens for every method of JPA repository I use. When I save an entity and then try to find a values for mentioned entity I get an empty List since that entity does not exists in DB. So do I have to change my approach or is there a way to wrap all of test method contents into a single transaction that will be rolledback after test method is done running?
Service that is tested:
#Service
public class FridgeServiceImpl implements FridgeService {
private final FridgeRepository fridgeRepository;
public FridgeServiceImpl(FridgeRepository fridgeRepository) {
this.fridgeRepository = fridgeRepository;
}
#Override
public Fridge save(Fridge fridge) {
return fridgeRepository.save(fridge);
}
#Override
public List<IngredientInFridge> getAllInFridge(Long id) {
return fridgeRepository.findAllInFridge(id);
}
#Override
public List<IngredientInFridge> getAllToExpireAfterDate(LocalDate date) {
return fridgeRepository.findAllWithExpirationDateAfter(date);
}
Repository:
#Repository
#Transactional
public interface FridgeRepository extends JpaRepository<Fridge, Long> {
#Query(value = "select i from Fridge f join f.fridgeContents i where i.id.fridgeId = :fridgeId")
List<IngredientInFridge> findAllInFridge(#Param("fridgeId") Long fridgeId);
#Query(value = "select i from IngredientInFridge i inner join IngredientGrammage ig on i.ingredient.id = ig.ingredient.id where i.fridge.id = :fridgeId " +
"and ig.recipe.id = :recipeId")
Set<IngredientInFridge> getIngredientsAvailableForRecipe(#Param("recipeId") Long recipeId, #Param("fridgeId") Long fridgeId);
#Query(value = "select i from IngredientInFridge i where i.expirationDate <= :date")
List<IngredientInFridge> findAllWithExpirationDateAfter(#Param("date") LocalDate date);
Test class:
#ExtendWith(SpringExtension.class)
#ContextConfiguration(classes = {TestConfig.class})
#Transactional
class FridgeServiceImplTest {
#Autowired
FridgeService fridgeService;
Ingredient ingredient;
Fridge fridge;
IngredientInFridge ingredientInFridge;
#BeforeEach
public void setUp() {
ingredient = new Ingredient(100, "Chicken", UnitOfMeasure.G);
fridge = new Fridge(new HashSet<>());
ingredientInFridge = new IngredientInFridge(ingredient, fridge, 50, LocalDate.of(2020, 12, 30));
fridge.addToFridge(ingredientInFridge);
}
#Test
void getAllInFridge_ThenReturn_1() {
//given
var savedFridge = fridgeService.save(fridge); //save an entity
//when
var ingredientsInFridge = fridgeService.getAllInFridge(savedFridge.getId()); //empty List since fridgeService.save(fridge) was rolledback.
//then
Assertions.assertEquals(1, savedFridge.getFridgeContents().size());
Assertions.assertEquals(1, ingredientsInFridge.size()); // failed assertion
}
Test config:
#Configuration
#EnableJpaRepositories(value = "com.calculate.calories.repository")
#PropertySource("classpath:applicationTest.properties")
#ComponentScan(basePackages = {"com.calculate.calories.service", "com.calculate.calories.repository"})
#EntityScan("com.calculate.calories.model")
public class TestConfig {
#Autowired
private Environment environment;
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean em
= new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dataSource());
em.setPackagesToScan("com.calculate.calories.model");
JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
em.setJpaVendorAdapter(vendorAdapter);
em.setJpaProperties(additionalProperties());
return em;
}
#Bean
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(Objects.requireNonNull(environment.getProperty("spring.datasource.driver-class-name")));
dataSource.setUrl(Objects.requireNonNull(environment.getProperty("spring.datasource.url")));
dataSource.setUsername(Objects.requireNonNull(environment.getProperty("spring.datasource.username")));
dataSource.setPassword(Objects.requireNonNull(environment.getProperty("spring.datasource.password")));
return dataSource;
}
#Bean
public PlatformTransactionManager transactionManager() {
//JpaTransactionManager transactionManager = new JpaTransactionManager();
//transactionManager.setEntityManagerFactory(entityManagerFactory().getObject());
return new DataSourceTransactionManager(dataSource());
}
#Bean
public PersistenceExceptionTranslationPostProcessor exceptionTranslation() {
return new PersistenceExceptionTranslationPostProcessor();
}
Properties additionalProperties() {
Properties properties = new Properties();
properties.setProperty("hibernate.hbm2ddl.auto", "none");
properties.setProperty("hibernate.dialect", "org.hibernate.dialect.MySQL5Dialect");
return properties;
}
I tried to use JpaTransactionManager instead of DataSourceTransactionManager but if I do so, there are no rollbacks for the tests and db is flooded with duplicate data. I've also attempted changing
#Transactional from class level to method level but it changed nothing.
I've seen this post: Does #Transactional annotation on test methods rollback every transaction in the annotated method before it gets to the end?
But when I try to use the EntityManager.flush() after saving it throws javax.persistence.TransactionRequiredException: no transaction is in progress

org.springframework.orm.jpa.JpaSystemException: could not initialize proxy [Order#orderdate] - no Session;

My spring boot v2.5.5 application has master slave mysql db configured.
Primary Data Source Config
#EnableJpaRepositories(
basePackages = "com.dummy",
excludeFilters = #ComponentScan.Filter(ReadOnlyRepository.class),
entityManagerFactoryRef = "primaryEntityManagerFactory",
transactionManagerRef = "primaryTransactionManager"
)
#Configuration
public class PrimaryDataSourceConfig {
#Autowired
private Environment env;
#Value("${master.jdbc.url}")
private String jdbcURL;
#Bean
#Primary
public DataSource primaryDataSource() throws Exception {
return DataSourceBuilder.create()
.driverClassName("com.mysql.jdbc.Driver")
.url(jdbcURL)
.username("root")
.password("root").build();
}
#Bean
#Primary
public LocalContainerEntityManagerFactoryBean primaryEntityManagerFactory() throws Exception {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(primaryDataSource());
em.setPackagesToScan("com.dummy");
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setGenerateDdl(false);
em.setJpaVendorAdapter(vendorAdapter);
em.setJpaProperties(additionalProperties());
return em;
}
#Bean
#Primary
public PlatformTransactionManager primaryTransactionManager() throws Exception {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(primaryEntityManagerFactory().getObject());
return transactionManager;
}
final Properties additionalProperties() {
final Properties hibernateProperties = new Properties();
hibernateProperties.setProperty("hibernate.hbm2ddl.auto", env.getProperty("primary.hibernate.hbm2ddl.auto"));
hibernateProperties.setProperty("hibernate.dialect", env.getProperty("primary.hibernate.dialect"));
hibernateProperties.setProperty("hibernate.show_sql", env.getProperty( "primary.hibernate.show_sql"));
hibernateProperties.setProperty("hibernate.cache.use_second_level_cache", "false");
return hibernateProperties;
}
}
Slave Datasource Config
#Configuration
#EnableJpaRepositories(
basePackages = "com.dummy.submodule",
includeFilters = #ComponentScan.Filter(ReadOnlyRepository.class),
entityManagerFactoryRef = "readOnlyEntityManagerFactory",
transactionManagerRef = "readOnlyTransactionManager"
)
public class ReadOnlyDataSourceConfig {
#Autowired
private Environment env;
#Value("${slave.jdbc.url}")
private String jdbcURL;
#Bean
public DataSource readOnlyDataSource() throws Exception {
return DataSourceBuilder.create()
.driverClassName("com.mysql.jdbc.Driver")
.url(jdbcURL)
.username("root")
.password("root").build();
}
#Bean
public LocalContainerEntityManagerFactoryBean readOnlyEntityManagerFactory() throws Exception {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(readOnlyDataSource());
em.setPackagesToScan("com.dummy.submodule");
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setGenerateDdl(false);
em.setJpaVendorAdapter(vendorAdapter);
em.setJpaProperties(additionalProperties());
return em;
}
#Bean
public PlatformTransactionManager readOnlyTransactionManager() throws Exception {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(readOnlyEntityManagerFactory().getObject());
return transactionManager;
}
final Properties additionalProperties() {
final Properties hibernateProperties = new Properties();
hibernateProperties.setProperty("hibernate.hbm2ddl.auto", env.getProperty("readonly.hibernate.hbm2ddl.auto"));
hibernateProperties.setProperty("hibernate.dialect", env.getProperty("readonly.hibernate.dialect"));
hibernateProperties.setProperty("hibernate.show_sql", env.getProperty( "readonly.hibernate.show_sql"));
hibernateProperties.setProperty("hibernate.cache.use_second_level_cache", "false");
return hibernateProperties;
}
}
Entity
#Entity
#Getter
#Setter
public class Product implements Serializable {
private static final long serialVersionUID = 8135071385764991866L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#OneToOne(fetch = FetchType.LAZY)
#NotNull
#JoinColumn(name = "status")
private Order order
}
#Getter
#Setter
#Entity
public class Order implements Serializable {
private static final long serialVersionUID = 8135071385764991879L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private Date orderdate;
}
I am trying to fetch Product from read only(slave) db in my service layer
#Service
#AllArgsConstructor
public class ProductService{
private ProductRepository repo;
public void performOperation(){
Optional<Product> product = repo.findById(1l, Product.class);
if(product.isPresent()){
Order order = product.get().getOrder();
Date orderDate = order.getOrderDate(); // this line gives below exception
}
}
}
Readonly Repository:
#Repository
#ReadOnlyRepository
public interface ProductRepository extends JpaRepository<Product, Long> {
<T> Optional<T> findById(final Long id, Class<T> type);
}
Exception:
2021-10-22 12:35:11,109 ERROR [http-nio-8080-exec-8] com.dummy.logging.LoggingClass:41 Exception_Occurred::{}
org.springframework.orm.jpa.JpaSystemException: could not initialize proxy [com.dummy.submodule.entities.Order#orderdate] - no Session; nested exception is org.hibernate.LazyInitializationException: could not initialize proxy [com.dummy.submodule.entities.Order#orderdate] - no Session
Note: The LazyInitializationException occurs only when I am fetching Product from slave db. When same operation i am trying to perform using master db I am not getting LazyInitializationException.
This issue for now i have handled by making Order as FetchType.EAGER.
But i am trying to find out reason behind this in case of master slave db and how to achieve it with LAZY fetch type.
Annotate performOperation with #Transactional like below:
#Service
#AllArgsConstructor
public class ProductService{
private ProductRepository repo;
#Transactional
public void performOperation(){
Optional<Product> product = repo.findById(1l, Product.class);
if(product.isPresent()){
Order order = product.get().getOrder();
Date orderDate = order.getOrderDate(); // this line gives below exception
}
}
}

Junit test cases for JPA repositories

I am new to junit, I have a repository as follows:
#Repository
public interface ChartRepository extends JpaRepository<Chart, Integer>{
}
and My Chart Entity class is follows:
#Entity
#Table(name = "Chart")
public class Chart {
#Column(name = "ENT_ID")
private String entID;
#Column(name = "ent_NAME")
private String entName;
#Column(name = "ent_PRODUCER_ID")
private String entProducerId;
#Id
#Column(name = "ent_Rc_ID")
#SequenceGenerator(name = "ent_RC_ID_SEQ", sequenceName="ent_RC_ID_SEQ", allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "ent_RC_ID_SEQ")
private Integer entReceiveId;
#Column(name = "JOB_ID")
private Integer jobId;
#Column(name = "CREATE_DT")
private Timestamp createdDate;
//getters and Setters
}
Now, Can we able to write test cases for the repository class. If so how can we do that.Can anyone please suggest me with some code samples.
You can create a #DataJpaTest and #Autowire your repository into it. For example:
#RunWith(SpringRunner.class)
#DataJpaTest
public class MyJpaTest {
#Autowired
private ChartRepository chartRepository;
#Test
public void myTest() {
...
}
}
See this for more: https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-testing.html#boot-features-testing-spring-boot-applications-testing-autoconfigured-jpa-test
I would strongly recomment to use any in-memory DB to test you JPA repository and dont use mock test framework like Mockito, EasyMock, etc. As in Dao layer, there should not be any business logic to mock. it should be simple read/write operation.
I use h2database for this.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(
classes = {DatabaseConfig.class},
loader = AnnotationConfigContextLoader.class)
public class ChartRepositoryTest {
#Autowired
private ChartRepository cartRepository;
#Test
public void testfind() {
// do find , insert and validate the response
}
}
testCompile('com.h2database:h2:1.4.196')
This is wha database config file looks like
#Configuration
#EnableJpaRepositories(basePackages = "com.mypackage.repository")
#PropertySource("application-test.properties")
#EnableTransactionManagement
public class DatabaseConfig {
#Autowired
private Environment env;
#Bean
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(env.getProperty("jdbc.driverClassName"));
dataSource.setUrl(env.getProperty("jdbc.url"));
dataSource.setUsername(env.getProperty("jdbc.user"));
dataSource.setPassword(env.getProperty("jdbc.pass"));
return dataSource;
}
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dataSource());
em.setPackagesToScan(new String[] { "com.mypackage.v2" });
JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
em.setJpaVendorAdapter(vendorAdapter);
em.setJpaProperties(additionalProperties());
return em;
}
#Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory emf){
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(emf);
return transactionManager;
}
Properties additionalProperties() {
Properties properties = new Properties();
properties.setProperty("hibernate.hbm2ddl.auto", "create");
properties.setProperty("hibernate.dialect","org.hibernate.dialect.H2Dialect");
return properties;
}
}

Spring JPA Specify entity's table name on the repository interface

I have a scenario where I want to create multiple repositories for the same entity class.
The first and simplest scenario I want to save some of the instances of my class as rows in a different table than the primary table (which is designated on the entity itself).
other scenarios would be to create remote back ups hence the whole datasource would be different.
Does Spring allow things like that?
I'm not sure you'll want this, but here it is anyway... The best I could find using Spring Data Repositories is the following:
(1a) Manually define 2 repository beans to use 2 datasources:
#Bean
#Qualifier("db1")
public ModelJpaRepository modelJpaRepositoryDb1() {
JpaRepositoryFactoryBean<ModelJpaRepository, Model, String> myFactory = new JpaRepositoryFactoryBean<ModelJpaRepository, Model, String>();
myFactory.setRepositoryInterface(ModelJpaRepository.class);
myFactory.setEntityManager(entityManagerFactory1().createEntityManager());
myFactory.afterPropertiesSet();
return myFactory.getObject();
}
#Bean
#Qualifier("db2")
public ModelJpaRepository modelJpaRepositoryDb2() {
JpaRepositoryFactoryBean<ModelJpaRepository, Model, String> myFactory = new JpaRepositoryFactoryBean<ModelJpaRepository, Model, String>();
myFactory.setRepositoryInterface(ModelJpaRepository.class);
myFactory.setEntityManager(entityManagerFactory2().createEntityManager());
myFactory.afterPropertiesSet();
return myFactory.getObject();
}
(1b): Define the 2 datasources (referred to by the previous repository definitions):
#Bean(name = "dataSource1")
public DataSource dataSource1() {
BasicDataSource bds = new BasicDataSource();
bds.setDriverClassName(...);
bds.setUrl(...);
bds.setUsername(...);
bds.setPassword(...);
return bds;
}
#Bean(name = "dataSource2")
public DataSource dataSource2() {
BasicDataSource bds = new BasicDataSource();
bds.setDriverClassName(...);
bds.setUrl(...);
bds.setUsername(...);
bds.setPassword(...);
return bds;
}
(1c): Define 2 entityManagers - 1 per datasource (referred to by the previous repository definitions):
#Bean
public EntityManagerFactory entityManagerFactory1() {
LocalContainerEntityManagerFactoryBean entityManagerFactory = new LocalContainerEntityManagerFactoryBean();
entityManagerFactory.setDataSource(dataSource1());
entityManagerFactory.setPersistenceUnitName("pu1");
entityManagerFactory.setJpaVendorAdapter(jpaVendorAdapter());
entityManagerFactory.setJpaProperties(jpaProperties());
entityManagerFactory.afterPropertiesSet();
return entityManagerFactory.getNativeEntityManagerFactory();
}
#Bean
public EntityManagerFactory entityManagerFactory2() {
LocalContainerEntityManagerFactoryBean entityManagerFactory = new LocalContainerEntityManagerFactoryBean();
entityManagerFactory.setDataSource(dataSource2());
entityManagerFactory.setPersistenceUnitName("pu2");
entityManagerFactory.setJpaVendorAdapter(jpaVendorAdapter());
entityManagerFactory.setJpaProperties(jpaProperties());
entityManagerFactory.afterPropertiesSet();
return entityManagerFactory.getNativeEntityManagerFactory();
}
(2) Inject the 2 repositories and use them:
#Autowired
#Qualifier("db1")
private ModelJpaRepository modelJpaRepositoryDb1;
#Autowired
#Qualifier("db2")
private ModelJpaRepository modelJpaRepositoryDb2;
...
modelJpaRepositoryDb1.save(model);
modelJpaRepositoryDb2.save(model);
(3) Define the "OtherModel" and include the "Model" as embedded:
#Entity
#Table(name = "OTHER_TABLE")
public class OtherTable {
#Id
#Column(name = "ID", nullable = false)
private long id;
#Embedded
private Model model;
public OtherTable(Model model) {
this.model = model;
this.id = model.getId();
}
}
For info, This answer suggests using #Inheritance(strategy=InheritanceType.TABLE_PER_CLASS), to achieve define an additional model (inheritance vs composition).
(4) Define the 2 repositories for the OtherTable:
#Bean
#Qualifier("db1")
public OtherTableJpaRepository otherTableJpaRepositoryDb1() {
JpaRepositoryFactoryBean<OtherTableJpaRepository, OtherTable, String> myFactory = new JpaRepositoryFactoryBean<OtherTableJpaRepository, OtherTable, String>();
myFactory.setRepositoryInterface(OtherTableJpaRepository.class);
myFactory.setEntityManager(entityManagerFactory1().createEntityManager());
myFactory.afterPropertiesSet();
return myFactory.getObject();
}
#Bean
#Qualifier("db2")
public OtherTableJpaRepository otherTableJpaRepositoryDb2() {
JpaRepositoryFactoryBean<OtherTableJpaRepository, OtherTable, String> myFactory = new JpaRepositoryFactoryBean<OtherTableJpaRepository, OtherTable, String>();
myFactory.setRepositoryInterface(OtherTableJpaRepository.class);
myFactory.setEntityManager(entityManagerFactory2().createEntityManager());
myFactory.afterPropertiesSet();
return myFactory.getObject();
}
(5) Inject and use them all:
#Autowired
#Qualifier("db1")
private ModelJpaRepository modelJpaRepositoryDb1;
#Autowired
#Qualifier("db2")
private ModelJpaRepository modelJpaRepositoryDb2;
#Autowired
#Qualifier("db1")
private OtherTableJpaRepository otherTableJpaRepositoryDb1;
#Autowired
#Qualifier("db2")
private OtherTableJpaRepository otherTableJpaRepositoryDb2;
// ...
modelJpaRepositoryDb1.save(model);
otherTableJpaRepositoryDb1.save(new OtherTable(model));
modelJpaRepositoryDb2.save(model);
otherTableJpaRepositoryDb2.save(new OtherTable(model));

Spring Boot - using JPA Repositories with Hibernate

I have a simple Spring Boot application with the following auto-configuration properties:
spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.url=jdbc:postgresql://localhost:5432/mywebapp
spring.datasource.username=username
spring.datasource.password=password
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
spring.jpa.hibernate.ddl-auto=validate
These work fine and I'm able to setup Spring Data JpaRepositories:
public interface UserRepository extends JpaRepository<User, String>
{
User findByName(String name);
}
...for the following entities:
#Entity
public class User
{
#Id
#GeneratedValue(generator = "uuid")
#GenericGenerator(name = "uuid", strategy = "uuid2")
protected String uuid;
#Column(nullable = false)
private String name;
#Column(nullable = false)
private String password;
#Column(nullable = false)
private String email;
}
...and use them like this:
#Transactional
public void updatePassword(String username, String password)
{
User user = userRepository.findByName(username);
user.setEmail("test#example.com"); // This gets persisted automatically by the JpaRepository.
}
Now I'm struggling to configure the same thing manually. I've tried the following:
#Configuration
#EnableTransactionManagement
public class PersistenceConfig
{
#Bean
public DataSource dataSource()
{
DataSource dataSource = new DataSource();
dataSource.setDriverClassName("org.postgresql.Driver");
dataSource.setUrl("jdbc:postgresql://localhost:5432/mywebapp");
dataSource.setUsername("username");
dataSource.setPassword("password");
return dataSource;
}
#Bean
public LocalSessionFactoryBean sessionFactory()
{
LocalSessionFactoryBean sessionFactoryBean = new LocalSessionFactoryBean();
sessionFactoryBean.setDataSource(dataSource());
sessionFactoryBean.setPackagesToScan("com.example.persistent");
Properties hibernateProperties = new Properties();
hibernateProperties.setProperty("hibernate.dialect", "org.hibernate.dialect.PostgreSQLDialect");
hibernateProperties.setProperty("hibernate.hbm2ddl.auto", "validate");
sessionFactoryBean.setHibernateProperties(hibernateProperties);
return sessionFactoryBean;
}
#Bean
public HibernateTransactionManager transactionManager()
{
HibernateTransactionManager transactionManager = new HibernateTransactionManager();
transactionManager.setSessionFactory(sessionFactory().getObject());
return transactionManager;
}
}
...but while no exceptions are thrown and i can now successfully read from the database, it seems that none of the changes I make to the entities are being persisted.
Does anyone have an idea what I'm missing for the persistence to work?
OP here.
I seem to have misunderstood that JPA needs an EntityManager instead of a session.
The following configuration works:
#Configuration
#EnableTransactionManagement
public class PersistenceJpaConfig
{
#Bean
public DataSource dataSource()
{
DataSource dataSource = new DataSource();
dataSource.setDriverClassName("org.postgresql.Driver");
dataSource.setUrl("jdbc:postgresql://localhost:5432/mywebapp");
dataSource.setUsername("username");
dataSource.setPassword("password");
return dataSource;
}
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory()
{
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dataSource());
em.setPackagesToScan("com.example.persistent");
JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
em.setJpaVendorAdapter(vendorAdapter);
Properties properties = new Properties();
properties.setProperty("hibernate.dialect", "org.hibernate.dialect.PostgreSQLDialect");
properties.setProperty("hibernate.hbm2ddl.auto", "validate");
em.setJpaProperties(properties);
return em;
}
#Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory emf)
{
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(emf);
return transactionManager;
}
#Bean
public PersistenceExceptionTranslationPostProcessor exceptionTranslation()
{
return new PersistenceExceptionTranslationPostProcessor();
}
}

Resources