JPA #PreUpdate #Persist seems not working as expected - java

I have a problem for filling auditing fields by using #PreUpdate and #PrePersist. For instance When I'd like to update a client entity, the field updatedBy and updatedAt are still null; despite when I debug, the code of preUpdate() which is annotated with #PreUpdate is executed.
Below the code of AuditingField which is responsible for creating/updating the auditing fields in each JPA entity:
#Embeddable
#Getter
#Setter
#NoArgsConstructor
public class FieldAuditing implements Serializable {
#Column(name = "DATE_CREATION", updatable = false)
private Instant createdAt;
#Column(name = "DATE_MODIFICATION", updatable = false)
private Instant updatedAt;
#Column(name = "DATE_SUPRESSION", updatable = false)
private Instant deletedAt;
#Column(name = "AUTEUR_CREATION", updatable = false, length = 100)
private String createdBy;
#Column(name = "AUTEUR_MODIFICATION", updatable = false, length = 100)
private String updatedBy;
#Column(name = "AUTEUR_SUPRESSION", updatable = false, length = 100)
private String deletedBy;
#Column(name = "IS_SUPPRIMER", nullable = false, updatable = false)
private boolean isDeleted;
#PrePersist
public void prePersist() {
setCreatedAt(Instant.now());
setCreatedBy(LoggedInUser.get());
}
#PreUpdate
public void preUpdate() {
setUpdatedAt(Instant.now());
setUpdatedBy(LoggedInUser.get());
}
#PreRemove
public void preRemove() {
setDeletedAt(Instant.now());
setDeleted(true);
setDeletedBy(LoggedInUser.get());
}
}
The client entity that contains embedded auditing fields:
#Entity
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#Builder
#Table(name="TF_CLIENT", schema="dbo")
public class Client implements Serializable {
private static final long serialVersionUID = 8832848102370267801L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO, generator="native")
#GenericGenerator(name = "native", strategy = "native")
#Column(name = "CLT_ID", nullable = false)
private Long id;
#Column(name = "CLT_LIBELLE", nullable = false, length = 50, unique = true)
private String libelle;
#Temporal(TemporalType.DATE)
#Column(name = "CLT_DT_OUVERTURE", nullable = false)
private Date dateOuverture;
#Temporal(TemporalType.DATE)
#Column(name = "CLT_DT_FERMETURE")
private Date dateFermeture;
#Column(name = "CLT_B_ACTIF")
private boolean isActif;
#Embedded
private FieldAuditing fieldAuditing = new FieldAuditing() ;
//... rest of another attributes
}
The method that updates the client entity
private ClientDto save(ClientDto clientDto, Client client) {
startDateShouldBeBeforeEndDate(clientDto);
hasUniqueCodePaies(clientDto.getCodePaies());
Client clientSaved = clientRepository.save(clientMapper.toEntity(clientDto, client));
clientMapper.addOrRemoveClientActions(clientDto, clientSaved);
clientMapper.addOrRemoveClientEtats(clientDto, clientSaved);
clientRepository.save(clientSaved);
clientDto.setId(clientSaved.getId());
return clientDto;
}
And finally the persistence context configuration:
#Configuration
#PropertySource({"classpath:application.yml"})
#EnableJpaRepositories(
basePackages = "com.github.maaoutir.clientManager",
entityManagerFactoryRef = "mainEntityManager")
public class PersistenceContext {
private final Environment env;
public PersistenceContext(Environment env) {
this.env = env;
}
#Bean
#Primary
public DataSource mainDataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(Objects.requireNonNull(env.getProperty("spring.datasource.driverClassName")));
dataSource.setUrl(env.getProperty("spring.datasource.url"));
dataSource.setUsername(env.getProperty("spring.datasource.username"));
dataSource.setPassword(env.getProperty("spring.datasource.password"));
return dataSource;
}
#Bean
#Primary
public LocalContainerEntityManagerFactoryBean mainEntityManager() {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(mainDataSource());
em.setPackagesToScan("com.github.maaoutir.clientManager");
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
em.setJpaVendorAdapter(vendorAdapter);
HashMap<String, Object> properties = new HashMap<>();
// properties.put("hibernate.hbm2ddl.auto", env.getProperty("hibernate.hbm2ddl.auto"));
properties.put("hibernate.dialect", env.getProperty("spring.jpa.hibernate.dialect"));
em.setJpaPropertyMap(properties);
return em;
}
#Primary
#Bean
public PlatformTransactionManager transactionManager() {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(mainEntityManager().getObject());
return transactionManager;
}
}
I'm thankful for any help.

You are using updatable=false on those columns:
#Column(name = "DATE_MODIFICATION", updatable = false)
private Instant updatedAt;
#Column(name = "AUTEUR_MODIFICATION", updatable = false, length = 100)
private String updatedBy;
This means that JPA doesn't use this field to update the column. From the JPA spec for updatable:
Whether the column is included in SQL
UPDATE statements generated by the persistence
provider.
This make sense for the createdBy or createdAt columns, that are set on #PrePersist and persisted with the first INSERT, and you don't want them to be modified afterwards. But columns updated in #PreUpdate (or #PreRemove) wont be updated with the UPDATE statement if updatable is set to false

Check if you have #EnableJpaAuditing on your configuration, and #EntityListeners(AuditingEntityListener.class) on your entity class.

Create a constructor in the class #Embeddable with super.
It worked for me.
public FieldAuditing(){
super();
}

Related

How to connect to two databases using spring boot application?

I have to connect to two oracle databases from a spring boot application. Below is what I have done so far:
application.properties
#############################################
## Database Configuration
#############################################
# HikariCP settings
spring.datasource.hikari.minimumIdle=5
spring.datasource.hikari.maximumPoolSize=20
spring.datasource.hikari.idleTimeout=30000
spring.datasource.hikari.maxLifetime=2000000
spring.datasource.hikari.connectionTimeout=30000
spring.datasource.hikari.poolName=HikariPoolOrcl
# JPA settings
spring.jpa.database=default
spring.datasource.dialect=org.hibernate.dialect.OracleDialect
spring.jpa.hibernate.use-new-id-generator-mappings=false
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=update
spring.datasource.driver.class=oracle.jdbc.driver.OracleDriver
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.OracleDialect
spring.jpa.database-platform=org.hibernate.dialect.OracleDialect
############################################
# OracleDB connection settings
###########################################
spring.datasource.driver-class-name=oracle.jdbc.OracleDriver
spring.datasource.jdbc-url=jdbc:oracle:thin:#localhost:1521:orcl
spring.datasource.url=jdbc:oracle:thin:#localhost:1521:orcl
spring.datasource.username=userdev
spring.datasource.password=pass123
spring.datasource.pool-size=30
############################################
# OracleDB connection settings FOR IL DB
###########################################
spring.il.datasource.driver-class-name=oracle.jdbc.OracleDriver
spring.il.datasource.jdbc-url=jdbc:oracle:thin:#//192.126.98.77:1521/apimdbuat
spring.il.datasource.username=userdev
spring.il.datasource.password=Ahjhj20
spring.il.datasource.pool-size=30
the user model in the first database
#Data
#AllArgsConstructor
#NoArgsConstructor
#Entity(name = "User")
#Table(name = "users")
public class User {
#Id
#SequenceGenerator(name = "user_sequence", sequenceName = "user_sequence", allocationSize = 1
)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "user_sequence"
)
#Column(nullable = false, updatable = false)
private Long id;
#Column(nullable = false, length = 64)
#NotBlank(message = "Firstname is required")
private String firstname;
#NotBlank(message = "Lastname is required")
#Column(nullable = false, length = 64)
private String lastname;
#NotBlank(message = "Username is required")
#Column(nullable = false, length = 64, unique = true)
private String username;
#Column(nullable = false, length = 64, unique = true)
#Email
#NotEmpty(message = "Email is required")
private String email;
#NotBlank(message = "Password is required")
#Column(nullable = false, length = 64)
#JsonIgnore
private String password;
private String profileImgUrl;
private Date lastLoginDate;
private Date joinDate;
#JsonProperty("isActive")
private boolean isActive;
#JsonProperty("isNotLocked")
private boolean isNotLocked;
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JoinTable(name = "user_roles", joinColumns = #JoinColumn(name = "user_id", referencedColumnName = "id"), inverseJoinColumns = #JoinColumn(name = "role_id", referencedColumnName = "id")
)
#OnDelete(action = OnDeleteAction.CASCADE)
private Set<Role> roles = new HashSet<>();
and here is the model for the second database
#AllArgsConstructor
#NoArgsConstructor
#Entity
#Table(
name = "app_config"
)
public class AppConfig {
#Id
#Column(
name = "ID"
)
#GeneratedValue(
strategy = GenerationType.AUTO
)
private Long id;
private String appCode;
private String appName;
private String version;
}
Repositories:
public interface UserRepository extends JpaRepository<User, Long> {
boolean existsByEmail(String email);
boolean existsByUsername(String username);
}
public interface AppConfigRepository extends JpaRepository<AppConfig, Long> {
}
and finally the configuration classes
#Configuration(proxyBeanMethods = false)
#EnableTransactionManagement
#EnableJpaRepositories(
entityManagerFactoryRef = "primaryEntityManagerFactory",
transactionManagerRef = "primaryTransactionManager",
basePackages = {"com.app.models",
"com.app.repositories"}
)
public class PrimaryDatabaseConfig {
#Bean(name = "primaryDataSource")
#Primary
#ConfigurationProperties(prefix = "spring.datasource")
public DataSource primaryDataSource() {
return DataSourceBuilder.create().build();
}
#Bean
#ConfigurationProperties(prefix = "spring.datasource")
public HikariDataSource dataSource() {
return DataSourceBuilder.create().type(HikariDataSource.class).build();
}
#Primary
#Bean(name = "primaryEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean primaryEntityManagerFactory(EntityManagerFactoryBuilder builder,
#Qualifier("primaryDataSource") DataSource primaryDataSource) {
return builder
.dataSource(primaryDataSource)
.packages("com.app.repositories", "com.app.models")
.build();
}
#Bean(name = "primaryTransactionManager")
public PlatformTransactionManager primaryTransactionManager(
#Qualifier("primaryEntityManagerFactory") EntityManagerFactory primaryEntityManagerFactory) {
return new JpaTransactionManager(primaryEntityManagerFactory);
}
}
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(
entityManagerFactoryRef = "secondaryEntityManagerFactory",
transactionManagerRef = "secondaryTransactionManager",
basePackages = {"com.app.il_models",
"com.app.il_repositories"}
)
public class SecondaryDatabaseConfig {
#Bean(name = "secondaryDataSource")
#ConfigurationProperties(prefix = "spring.il.datasource")
public DataSource secondaryDataSource() {
return DataSourceBuilder.create().build();
}
#Bean(name = "secondaryEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean secondaryEntityManagerFactory(EntityManagerFactoryBuilder builder,
#Qualifier("secondaryDataSource") DataSource secondaryDataSource) {
return builder
.dataSource(secondaryDataSource)
.packages("com.app.il_models",
"com.app.il_repositories")
.build();
}
#Bean(name = "secondaryTransactionManager")
public PlatformTransactionManager secondaryTransactionManager(
#Qualifier("secondaryEntityManagerFactory") EntityManagerFactory secondaryEntityManagerFactory) {
return new JpaTransactionManager(secondaryEntityManagerFactory);
}
}
The first time I got exceptions related to jdbc url and after fixing it the application started normally but when I call the api of login which take username/password I get the following error
SQL Error: 904, SQLState: 42000
ORA-00904: "USER0_"."PROFILEIMGURL": invalid identifier
Unauthorized error: could not extract ResultSet; SQL [n/a]; nested exception is org.hibernate.exception.SQLGrammarException: could not extract ResultSet
{
"code": "401",
"message": "could not extract ResultSet; SQL [n/a]; nested exception is org.hibernate.exception.SQLGrammarException: could not extract ResultSet"
}
PS: Everything worked fine when it was one database.
Thank you.
You need to add two distinct datasources in the application.properties file. like this :
spring.datasource.url= jdbc:mysql://<host1>:<port>/<database1>
spring.datasource.username= <username1>
spring.datasource.password= <password1>
spring.second-datasource.url= jdbc:mysql://<host2>:<port>/<database2>
spring.second-datasource.username= <username2>
spring.second-datasource.password= <password2>
Then, create two separate EntityManagerFactoryBeans, like this .
#Primary
#Bean(name = "entityManagerFactory")
public LocalContainerEntityManagerFactoryBean entityManagerFactory(
EntityScanPackage entitiesScanPackage,
DataSource dataSource) {
LocalContainerEntityManagerFactoryBean emf = new LocalContainerEntityManagerFactoryBean();
emf.setDataSource(dataSource);
emf.setPackagesToScan(entitiesScanPackage.getPackageName());
emf.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
emf.setJpaProperties(jpaProperties());
return emf;
}
#Bean(name = "secondEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean secondEntityManagerFactory(
EntitiesScanPackage entitiesScanPackage,
#Qualifier("secondDataSource") DataSource dataSource) {
LocalContainerEntityManagerFactoryBean emf = new LocalContainerEntityManagerFactoryBean();
emf.setDataSource(dataSource);
emf.setPackagesToScan(entitiesScanPackage.getPackageName());
emf.setJpaVendorAdapter(new HibernateJpaVendor
Thank you.
For some reasons, after connecting to two databases at the same time, spring boot models attributes names does not match the names of columns in the database although it works fine when it was only one database.
To resolve that I had to annotate all attrbutes with #Column and force the fields names as they are in the database, I also forced the table names with #Table
#Column(name = "JOIN_DATE")
private Date joinDate;
#Table(name = "USERS")

Spring AuditorAware with custom User Entity

i am currently adding Auditing to my Entities. That is working fine but i want to save the Id of the User which is related to the Entity inside the #CreatedBy and #LastModifiedBy Columns and not the Name which i have right now.
I am also using Spring Security + JWT to get the User which is working fine.
How can i achieve this with the following Configuration?
User Entity:
#Entity(name = "User")
#Table(name = "user")
public class User extends Auditable<String> {
#Id
#GeneratedValue(
strategy = GenerationType.SEQUENCE,
generator = "UserSequence")
#GenericGenerator(
name = "UserSequence",
strategy = "com.simagdo.crmAPI.utils.SequenceIdGenerator",
parameters = {
#org.hibernate.annotations.Parameter(name = SequenceIdGenerator.INCREMENT_PARAM, value = "1"),
#org.hibernate.annotations.Parameter(name = SequenceIdGenerator.VALUE_PREFIX_PARAMETER, value = "130"),
#org.hibernate.annotations.Parameter(name = SequenceIdGenerator.NUMBER_FORMAT_PARAMETER, value = "%05d")
}
)
#Column(name = "Id")
private String id;
#Column(
name = "UserName",
length = 80,
unique = true)
private String userName;
#Column(name = "Password")
private String password;
}
AuditorAware:
public class AuditAwareImpl implements AuditorAware<String> {
#Override
public Optional<String> getCurrentAuditor() {
return Optional.of("Simagdo");
}
}
The config:
#Configuration
#EnableJpaAuditing(auditorAwareRef = "auditorAware")
public class JpaConfig {
#Bean
public AuditorAware<String> auditorAware() {
return new AuditAwareImpl();
}
}
My Auditable Class:
#MappedSuperclass
public abstract class Auditable<U> {
#CreatedBy
#OneToOne(
orphanRemoval = true,
cascade = {
CascadeType.PERSIST,
CascadeType.REMOVE
}
)
private User createdBy;
#CreatedDate
#Column(
name = "CreatedDate",
nullable = false,
updatable = false
)
private LocalDateTime createdDate;
#LastModifiedBy
#OneToOne(
orphanRemoval = true,
cascade = {
CascadeType.PERSIST,
CascadeType.REMOVE
}
)
private User lastModifiedBy;
#LastModifiedDate
#Column(
name = "LastModifiedDate",
nullable = false
)
private LocalDateTime lastModifiedDate;
}
try adding following on top of entity class that contains #CreatedBy & #LastModifiedBy:
#EntityListeners({AuditingEntityListener.class})

hibernate read different relation table name after changing to multiple data sources

I am working on a project using spring boot with version 1.3.5.RELEASE, it is using spring data jpa as persistent layer. I got a task to implement multiple datasource on this project.
After implemented the code we got the error, regarding these two entity class :
#Entity
#Table(name = "TBL_MERCHANT")
public class Merchant implements Serializable {
#Id
#Column(name = "ID", length = 14, precision = 14, nullable = false)
private BigInteger id;
#Column(name = "NAME", length = 100, nullable = false)
private String name;
#JoinColumn(name = "CATEGORY_ID")
#ManyToOne(fetch = FetchType.LAZY)
private Categorization categorization;
#OrderBy("id ASC")
#ManyToMany(mappedBy = "merchants", fetch = FetchType.LAZY)
private Set<PaymentMethod> paymentMethods = new HashSet<>();
#JoinColumn(name = "USER_ID")
#OneToOne(fetch = FetchType.LAZY)
private WalletUser user;
// getter setter
and
#Entity
#Table(name = "TBL_PAYMENT_METHOD")
public class PaymentMethod implements Serializable {
#Id
#Column(name = "ID", length = 24)
private String id;
#Column(name = "DESCRIPTION", length = 255, nullable = true)
private String desc;
#ManyToMany
private Set<Merchant> merchants = new HashSet<>();
// getter setter
}
previously, the relationship between these two entities generate a table named tbl_payment_method_merchants
but after the changes this kind of error occurred
ERROR: relation "tbl_payment_method_tbl_merchant" does not exist
the following is my changes :
Before
#EnableAsync
#EnableAutoConfiguration(exclude = {org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration.class})
#EntityScan("com.somepackage")
#ComponentScan({"com.somepackage})
#EnableJpaRepositories("com.somepackage")
#PropertySource(value = "classpath:stringMessage.properties")
public class MainApplication {
public static void main(String[] args) {
SpringApplication.run(MainApplication.class, args);
}
}
i separate it into two different data sources,as followings :
After :
first datasource :
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(
basePackages = RepositoryMetadataProvider.REPOSITORY_BASE_PACKAGE,
includeFilters = #ComponentScan.Filter(ReadOnlyConnection.class),
entityManagerFactoryRef = ReadOnlyRepositoryConfig.READ_ONLY_ENTITY_MANAGER_FACTORY,
transactionManagerRef = ReadOnlyRepositoryConfig.READ_ONLY_TRX_MANAGER
)
public class ReadOnlyRepositoryConfig {
public static final String READ_ONLY_TRX_MANAGER ="readOnlyTransactionManager";
public static final String READ_ONLY_ENTITY_MANAGER ="readOnlyEntityManager";
public static final String READ_ONLY_ENTITY_MANAGER_FACTORY ="readOnlyEntityManagerFactory";
#Bean(name = READ_ONLY_TRX_MANAGER)
PlatformTransactionManager readOnlyTransactionManager(
#Qualifier(READ_ONLY_ENTITY_MANAGER_FACTORY) EntityManagerFactory entityManagerFactory) {
return new JpaTransactionManager(entityManagerFactory);
}
#Bean(name = READ_ONLY_ENTITY_MANAGER)
EntityManager readOnlyEntityManager(#Qualifier(READ_ONLY_ENTITY_MANAGER_FACTORY) LocalContainerEntityManagerFactoryBean masterEntityFactory) {
return masterEntityFactory.getObject().createEntityManager();
}
#Singleton
#Bean(name = READ_ONLY_ENTITY_MANAGER_FACTORY)
LocalContainerEntityManagerFactoryBean readOnlyEntityFactory(
EntityManagerFactoryBuilder builder,
#Qualifier(DataSourceConfig.WALLET_SLAVE_DATASOURCE) DataSource dataSource) {
LocalContainerEntityManagerFactoryBean localContainerEntityManagerFactoryBean = builder
.dataSource(dataSource)
.packages(RepositoryMetadataProvider.REPOSITORY_BASE_PACKAGE)
.build();
localContainerEntityManagerFactoryBean.setMappingResources(new String[]{"META-INF/orm.xml", "META-INF/bv-orm.xml", "META-INF/infinitium-orm.xml", "META-INF/cam-orm.xml"});
return localContainerEntityManagerFactoryBean;
}
}
second datasource :
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(
basePackages = RepositoryMetadataProvider.REPOSITORY_BASE_PACKAGE,
excludeFilters =#ComponentScan.Filter(ReadOnlyConnection.class),
entityManagerFactoryRef = TransactionalRepositoryConfig.TRANSACTIONAL_ENTITY_MANAGER_FACTORY,
transactionManagerRef = TransactionalRepositoryConfig.TRANSACTIONAL_TRX_MANAGER
)
public class TransactionalRepositoryConfig {
public static final String TRANSACTIONAL_TRX_MANAGER ="transactionalTransactionManager";
public static final String TRANSACTIONAL_ENTITY_MANAGER ="transactionalEntityManager";
public static final String TRANSACTIONAL_ENTITY_MANAGER_FACTORY ="transactionalEntityManagerFactory";
#Primary
#Bean(name = TRANSACTIONAL_TRX_MANAGER)
PlatformTransactionManager masterTransactionManager(
#Qualifier(TRANSACTIONAL_ENTITY_MANAGER_FACTORY) EntityManagerFactory entityManagerFactory) {
return new JpaTransactionManager(entityManagerFactory);
}
#Primary
#Bean(name = TRANSACTIONAL_ENTITY_MANAGER)
EntityManager masterEntityManager(#Qualifier(TRANSACTIONAL_ENTITY_MANAGER_FACTORY) LocalContainerEntityManagerFactoryBean masterEntityFactory){
return masterEntityFactory.getObject().createEntityManager();
}
#Singleton
#Primary
#Bean(name = TRANSACTIONAL_ENTITY_MANAGER_FACTORY)
LocalContainerEntityManagerFactoryBean masterEntityFactory(
EntityManagerFactoryBuilder builder,
#Qualifier(DataSourceConfig.WALLET_MASTER_DATASOURCE) DataSource dataSource) {
LocalContainerEntityManagerFactoryBean localContainerEntityManagerFactoryBean = builder
.dataSource(dataSource)
.packages(RepositoryMetadataProvider.REPOSITORY_BASE_PACKAGE)
.build();
localContainerEntityManagerFactoryBean.setMappingResources(new String[]{"META-INF/orm.xml", "META-INF/bv-orm.xml", "META-INF/infinitium-orm.xml", "META-INF/cam-orm.xml"});
return localContainerEntityManagerFactoryBean;
}
}
org.hibernate.engine.jdbc.spi.SqlExceptionHelper : ERROR: relation "tbl_payment_method_tbl_merchant" does not exist, when using multiple datasource
#Entity
#Table(name = "TBL_MERCHANT")
public class Merchant implements Serializable {
#OrderBy("id ASC")
#ManyToMany(mappedBy = "merchants", fetch = FetchType.LAZY)
#JoinTable(name = "tbl_payment_method_merchants",
joinColumns = #JoinColumn(name = "idMerchant", referencedColumnName = "idMerchant"),
inverseJoinColumns = #JoinColumn(name = "idPaymentMethod", referencedColumnName = "idPaymentMethod"))
private Set<PaymentMethod> paymentMethods = new HashSet<>();
// getter setter
#Entity
#Table(name = "TBL_PAYMENT_METHOD")
public class PaymentMethod implements Serializable {
#ManyToMany
#JoinTable(name = "tbl_payment_method_merchants",
joinColumns = #JoinColumn(name = "idPaymentMethod", referencedColumnName = "idPaymentMethod"),
inverseJoinColumns = #JoinColumn(name = "idMerchant", referencedColumnName = "idMerchant"))
private Set<Merchant> merchants = new HashSet<>();
// getter setter
}
Can you try above. This will ensure that the metadata for the many-to-many relationship is available on both the entities.
I also suggest replacing FetchType.EAGER with FetchType.LAZY.

How can you make a created_at column generate the creation date-time automatically like an ID automatically gets created?

I currently have an Entity as below:
#Entity
public class Product {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long productId;
private String productImage;
private String productTitle;
private String productDescription;
private Integer productPrice;
private Date createdAt;
private Date updatedAt;
Upon creation of this object, the value of createdAt and updatedAt shows null in the database and was wondering how I can implement code so that createdAt and updateAt automatically gets inserted?
My post method is as below:
#PostMapping("/products")
public ProductResponse createProduct(#Validated #RequestBody ProductForm productForm) {
Product product = productForm.asProduct();
Product createdProduct = productRepository.save(product);
return new ProductResponse(createdProduct, "Product created");
}
JPA
There isn't anything as convenient as annotating the Timestamp field directly but you could use the #PrePersist, #PreUpdate annotations and with little effort achieve the same results.
Hibernate
#CreationTimestamp - Documentation
#UpdateTimestamp - Documentation
Spring Data JPA
#CreatedDate - Documentation
#LastModifiedDate - Documentation
Extend the following abstract class in your entity:
#MappedSuperclass
#EntityListeners(AuditingEntityListener.class)
public abstract class DateAudit implements Serializable {
#CreatedDate
#Column(name = "created_at", nullable = false, updatable = false)
private Date createdAt;
#LastModifiedDate
#Column(name = "updated_at")
private LocalDateTime updatedAt;
}
Don't forget to enable JPA Auditing feature using #EnableJpaAuditing
Read this: https://docs.spring.io/spring-data/jpa/docs/1.7.0.DATAJPA-580-SNAPSHOT/reference/html/auditing.html
With the mix of #dimitrisli and #buddha answers, something pretty clean is
#Data
#MappedSuperclass
public abstract class BaseEntity {
#Column(updatable = false)
#CreationTimestamp
private LocalDateTime createdAt;
#UpdateTimestamp
private LocalDateTime updatedAt;
}
And now you all your entity can extend that class like so
#Data
#Entity
#EqualsAndHashCode(callSuper = true)
public class User extends BaseEntity {
#Id
#GeneratedValue
public UUID id;
public String userName;
public String email;
public String firstName;
public String lastName;
}
Note that you might not need #Data & #EqualsAndHashCode annotations from lombok as it generate getter/setter
You can create a BaseEntity. Each entity extends the BaseEntity. In the Base entity ,it will set the time automatically
#Data
#MappedSuperclass
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class BaseEntity implements Serializable {
#Id
#Column(name = "Id")
private String id;
#Column(name = "deleted", columnDefinition = "Bit(1) default false")
private boolean deleted = false;
#Column(name = "DataChange_CreatedBy", nullable = false)
private String dataChangeCreatedBy;
#Column(name = "DataChange_CreatedTime", nullable = false)
private Date dataChangeCreatedTime;
#Column(name = "DataChange_LastModifiedBy")
private String dataChangeLastModifiedBy;
#Column(name = "DataChange_LastTime")
private Date dataChangeLastModifiedTime;
#PrePersist
protected void prePersist() {
if (this.dataChangeCreatedTime == null) dataChangeCreatedTime = new Date();
if (this.dataChangeLastModifiedTime == null) dataChangeLastModifiedTime = new Date();
}
#PreUpdate
protected void preUpdate() {
this.dataChangeLastModifiedTime = new Date();
}
#PreRemove
protected void preRemove() {
this.dataChangeLastModifiedTime = new Date();
}
}

Hibernate asking for javax.validation.constraints.Size for enum

Working with dropwizard and hibernate
Exception when I try to do a persist is
No validator could be found for constraint
'javax.validation.constraints.Size' validating type
'enums.ServiceType'. Check configuration for 'type'
Code is below:
#Table(name = "transactions",
indexes = {
#Index(name = "references_index", columnList = "reference_id")
}
)
public class Transaction {
#JsonIgnore
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private long id;
#NonNull
#NotEmpty
#Column(name = "reference_id", unique = true)
private String referenceId;
#NonNull
#NotEmpty
#Column(name = "state")
private String state;
#NonNull
#Column(name = "type")
#Enumerated(EnumType.STRING)
private ServiceType type;
#NonNull
#NotEmpty
#Column(name = "provider")
private String provider;
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "created_date", insertable = false, updatable = false)
private Date createdTimeStamp;
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "last_updated", insertable = false, updatable = true)
private Date updatedTimeStamp;
public Transaction(String referenceId, String state, ServiceType type, String provider) {
this.referenceId = referenceId;
this.state = state;
this.type = type;
this.provider = provider;
}
}
where
public enum ServiceType {
TYPEA, TYPEB, TYPEC
}
doing a persist in the DAO
class TransactionDAO extends AbstractDAO<Transaction> {
/**
* Creates a new DAO with a given session provider.
*
* #param sessionFactory a session provider
*/
public TransactionDAO(SessionFactory sessionFactory) {
super(sessionFactory);
}
protected Transaction persistTransaction(Transaction transaction) {
return persist(transaction);
}
}
Adding a #Valid against my enum solves this. However I don't know why the error message said it was trying javax.validation.constraints.Size
code snippet from working code
#NonNull
#Valid
#Column(name = "type")
#Enumerated(EnumType.STRING)
private ServiceType type;

Categories