I have one entity and I want to track all changes therefore I created new Entity for audit.
Below is my primary entity:
#Data
#NoArgsConstructor
#AllArgsConstructor
#Entity
#EntityListeners(AuditingEntityListener.class)
#Table(name = "primary")
public class PrimaryEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "primary_id")
private Long id;
private String name;
#LazyCollection(LazyCollectionOption.FALSE)
#ElementCollection
#CollectionTable(
name = "primary_attachments",
joinColumns = #JoinColumn(name = "primary_id")
)
private List<String> attachments;
#CreatedDate
#Temporal(TemporalType.DATE)
private Date createDate;
#LastModifiedDate
#Temporal(TemporalType.DATE)
private Date lastModifiedDate;
}
And below is my entity for audit:
#Data
#NoArgsConstructor
#AllArgsConstructor
#Entity
#EntityListeners(AuditingEntityListener.class)
#Table(name = "primary_audit")
public class PrimaryEntityAudit {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "audit_id")
private Long id;
#NotNull
#Column(name = "primary_entity_id")
private Long primaryId;
private String name;
#LazyCollection(LazyCollectionOption.FALSE)
#ElementCollection
#CollectionTable(
name = "primary_attachments_audit",
joinColumns = #JoinColumn(name = "primary_entity_id")
)
private List<String> attachments = new ArrayList<>();
#CreatedDate
#Temporal(TemporalType.DATE)
private Date createDate;
public PrimaryEntityAudit(PrimaryEntity primaryEntity) {
this.primaryId = primaryEntity.getId();
this.attachments.addAll(primaryEntity.getAttachments());
this.createDate = new Date();
}
}
And before update primary entity I create new PrimaryEntityAudit and save this object and then update the primary entity.
And operation is successful and object PrimaryEntityAudit is saved, but attachments from PrimaryEntityAudit aren't saved.
I tried also in constructor of ProjectEntityAudit do setAttachments, but then I got an Exception: HibernateExcpetion: Found shared references to collection.
How should I map the collections of audit for saving old state of PrimaryEntity attachments?
You should look at the following hibernate module Envers
It provides features for versioning and auditing
It is preferable to not reinvent the wheel except if you have technicals constraints which prevent you to use some frameworks or others.
Related
I have following entities:
#Entity
#Getter
#Setter
#Table(name = "nomenclature", schema = "public")
public class Nomenclature implements Serializable {
#Id
#Column(name = "id")
private Long id;
#Column(name = "name")
private String name;
#OneToMany
#JoinTable(name="nomenclature_versions",
joinColumns= #JoinColumn(name="nomenclature_id", referencedColumnName="id"))
private List<NomenclatureVersion> version;
}
and
#Entity
#Setter
#Getter
#Table(name = "nomenclature_versions", schema = "public")
#NoArgsConstructor
public class NomenclatureVersion implements Serializable {
#Id
#Generated(GenerationTime.INSERT)
#Column(name = "id")
private Long id;
#Column(name = "nomenclature_id")
private Long nomenclatureId;
#Column(name = "user_id")
private Long userId;
#Column(name = "creation_date")
private LocalDateTime creationDate;
#Column(name = "puls_code")
private String pulsCode;
#Column(name = "pic_url")
private String picUrl;
#Column(name = "current")
private boolean current;
}
When im trying to get Nomenclature with JPARepository getById(id) method im getting org.postgresql.util.PSQLException: ERROR: column version0_.version_id does not exist
It feels like the problem is around Hibernate Naming Strategies but i cant solve it.
Is there any other way to let Hibernate know which column should it use to join tables?
Don't use #JoinTable, the table is the same as the entity you are referencing. You just have to specify the #JoinColumn, hibernate will look for the column in the table referenced by the entity you are mapping the list to, NomenclatureVersion :
#OneToMany
#JoinColumn(name="nomenclature_id")
private List<NomenclatureVersion> version;
I'm using SpringBoot and JPA + Hibernate.
I have these entities:
#Entity
#Table(name = "post")
#AllArgsConstructor #NoArgsConstructor
#Getter #Setter
public class Item extends BaseEntity {
#Id
#Column(name = "id")
#Type(type = "uuid-char")
private UUID uuid = UUID.randomUUID();
#OneToMany(mappedBy = "post", orphanRemoval=true, fetch = FetchType.EAGER)
#MapKey(name="userId")
private Map<UUID, Share> shares;
....
#CreationTimestamp
#Column(name = "creation_date")
#Setter(AccessLevel.NONE)
private Instant creationDate;
#UpdateTimestamp
#Column(name = "last_mod_date")
#Setter(AccessLevel.NONE)
private Instant lastModificationDate;
}
#Entity
#Table(name = "comment"
#AllArgsConstructor #NoArgsConstructor
#Getter #Setter
public class Comment extends BaseEntity {
#Id
#Column(name = "id")
#Type(type = "uuid-char")
#Setter(AccessLevel.NONE)
private UUID uuid = UUID.randomUUID();
#ManyToOne(fetch = FetchType.LAZY)
#NotNull()
#JoinColumn(name = "post_id")
private Post post;
.....
#CreationTimestamp
#Column(name = "creation_date")
#Setter(AccessLevel.NONE)
private Instant creationDate;
#UpdateTimestamp
#Column(name = "last_mod_date")
#Setter(AccessLevel.NONE)
private Instant lastModificationDate;
}
#Test
#Transactional
public void testDeleteItem() {
itemRepo.delete(item);
itemRepo.flush();
}
When I try to delete a parent entity (Post), all related entities Comments remains on the database.
Why doesn't works the cascade deletion?
OrphanRemoval doesn't work. Use cascade = CascadeType.ALL instead
I'm using SpringBoot with JPA and Hibernate.
For a specific entity I need to use a primary key composed by a relationship + a field.
Something like this:
#Entity
#Table(
name = "book",
uniqueConstraints=#UniqueConstraint(columnNames={"book_id",
"field_a"}))
public class Book extends MyBaseEntity {
#Id
#Column(name = "id")
#Type(type = "uuid-char")
private UUID uuid = UUID.randomUUID();
#Column(name = "name", nullable = false)
private String name;
#OneToMany(mappedBy = "book")
private List<Author> authors = new ArrayList<>();
}
#Entity
#Table(name = "author")
#IdClass(CustomID.class)
public class Author extends MyBaseEntity {
/* Here the composite key */
#Id
#ManyToOne()
#JoinColumn(name = "book_id")
private Book book;
#Id
#Column(name = "field_a", nullable = false)
#Type(type = "uuid-char")
private UUID fieldA;
#CreationTimestamp
#Column(name = "creation_date")
private Instant creationDate;
#UpdateTimestamp
#Column(name = "last_mod_date")
private Instant lastModificationDate;
}
public class CustomID implements Serializable {
private UUID book;
private UUID fieldA;
}
Using this approach, after some inserts on the DB, for Author entity book_id column contains values like these:
ÃFË5ÊL©T¾Äd
Why? Why there aren't Book primary keys values?
I'm working on a Spring-Boot project with a H2 database. I have two entities Portfolio and Report, and there is a many-to-many association between the two.
I want those entities to be audited, so I followed this tutorial to audit through an AuditorAware interface with custom fields.
The two entities are well audited, the columns are created in the database. However, the join table portfolio_reports is not audited. How can I audit the join table as well ?
Portfolio.java
#Entity
#Table(name = "portfolio")
public class Portfolio extends Auditable<String> implements Serializable {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "name")
#Unique
private String name;
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
#JoinTable(name = "portfolio_report", joinColumns = #JoinColumn(name = "portfolio_id"), inverseJoinColumns = #JoinColumn(name = "report_id"))
private List<Report> reports;
// Getters and setters
}
Report.java
#Entity
#Table(name = "report")
public class Report extends Auditable<String> implements Serializable {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "axioma_id")
private Long axiomaId;
#Column(name = "name")
private String name;
#AuditJoinTable
#ManyToMany(mappedBy = "reports", cascade = CascadeType.PERSIST, fetch = FetchType.LAZY)
private List<Portfolio> portfolios;
// Getters and setters
}
Auditable.java
#MappedSuperclass
#EntityListeners(AuditingEntityListener.class)
public abstract class Auditable<U> {
#Version
#Column(name = "version_no")
protected Long versionNo;
#CreatedDate
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "created_date")
protected Date createdDate;
#LastModifiedDate
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "modified_date")
protected Date modifiedDate;
}
AuditorAwareImpl.java
public class AuditorAwareImpl implements AuditorAware<String> {
#Override
public Optional<String> getCurrentAuditor() {
return Optional.of("Admin");
}
}
PersistenceConfiguration.java
#Configuration
#EnableJpaAuditing(auditorAwareRef = "auditorAware")
public class PersistenceConfiguration {
#Bean
public AuditorAware<String> auditorAware() {
return new AuditorAwareImpl();
}
}
Problem:
Clearly here Auditable should add some column to your intermediate table that maintains relation between Portfolio and Report and that table is created behind the scene and you don't have access to that table in your program. Only hibernate can use that table to maintain relation between your entities and do join operation.
Solution:
Here you should make Join table that maintain Many to Many relation between Portfolio and Report explicit so that you can have entity like PortfolioReport in your program that can extends from Auditable. Please read the following post to see how to do that: The best way to map a many-to-many association with extra columns when using JPA and Hibernate
I'm trying to build a multi-language database, so I've used this database design as a approach for mine.
Now I've two problems/questions:
I want to retrieve all LocalizedEvent for a given language and given categoryId. How can I make a inner join over the LocalizedCategory table with Hibernate Criteria API?
With SQL I would make this statement to get all LocalizedEvent + LocalizedCategory:
SELECT * FROM event e
INNER JOIN
localized_event le ON (le.event_id = e.event_id)
INNER JOIN
localized_category lc ON (lc.category_id = e.category_id)
WHERE
le.locale = 'de' AND lc.locale = 'de'
My current approach looks like this without getting the LocalizedCategory (with Criteria API):
Criteria c = session.createCriteria(LocalizedEvent.class, "localizedEvent");
c.createAlias("localizedEvent.event", "event");
c.createAlias("event.category", "category");
c.add(Restrictions.eq("category.categoryId", categoryId));
c.add(Restrictions.eq("localizedEvent.locale", language));
I think my mapping is not 100% correct. The entity LocalizedEvent should have a property localizedCategory, but I don't want to save the ID of this localizedCategory (therefore I'm using the #Transient annotation) in the LocalizedEvent table, e.g. using a ManyToOne relation (joining LOC_CATEGORY_ID). But I think it's not possible to do this, isn't it? I would have to map this transient field to LocalizedEvent "manually", because Hibernate is not supporting this mapping (if I'm right).
(Using JDBC this property/mapping would cause no problems, because I can easily make my inner joins and assign the property localizedCategory to the LocalizedEvent in a RowMapper or so).
My entities looks like this:
Event
#Entity
#Table(name = "EVENT")
public class Event {
#Id
#GeneratedValue
#Column(name = "EVENT_ID", unique = true)
private Long eventId;
#Column(name = "DATE")
private Date date;
#OneToMany(mappedBy = "event")
private Set<LocalizedEvent> localizedEvents;
#ManyToOne
#JoinColumn(name = "CATEGORY_ID")
private Category category;
}
LocalizedEvent
#Entity
#Table(name = "LOCALIZED_EVENT")
public class LocalizedEvent {
#Id
#GeneratedValue
#Column(name = "LOC_EVENT_ID")
private Long locEventId;
#ManyToOne
#JoinColumn(name = "EVENT_ID")
private Event event;
#Transient
private LocalizedCategory localizedCategory;
#Column(name = "DESCRIPTION")
private String description;
#Column(name = "LOCALE")
private String locale;
}
Category
#Entity
#Table(name = "CATEGORY")
public class Category {
#Id
#GeneratedValue
#Column(name = "CATEGORY_ID", unique = true)
private Long categoryId;
#OneToMany(mappedBy = "category")
private Set<LocalizedCategory> localizedCategories;
#OneToMany(mappedBy = "category")
private Set<Event> events;
}
LocalizedCategory
#Entity
#Table(name = "LOCALIZED_CATEGORY")
public class LocalizedCategory {
#Id
#GeneratedValue
#Column(name = "LOC_CATEGORY_ID")
private Long locCategoryId;
#ManyToOne
#JoinColumn(name = "CATEGORY_ID")
private Category category;
#Column(name = "NAME")
private String name;
#Column(name = "LOCALE")
private String locale;
}