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
Related
I work with an embedded H2 database in which I use the #OneToMany relationship to relate an entity instance (product) to multiple instances of the other entities (suppliers); it's useful when I have specific suppliers for a particular product.
However now, I want to associate all the suppliers with every single product; I don't want to generate in my supplier table different supplier records for each product, instead I want to have only 5 records (5 suppliers) in my supplier table which are associated to every single product, it few words I want to achieve something like "one to all", is it possible to do it using JPA annotations?
Product entity
#Entity
public class Product {
#Id
private String productCode;
#OneToMany
#JoinColumn(name = "supplier_id", referencedColumnName = "productCode")
private List<Supplier> suppliers;
}
Supplier entity
#Entity
public class Supplier {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
private String name;
}
Unidirectional #OneToMany association:
#Entity
public class Product {
#Id
// #Column(name = "id") maybe
// #GeneratedValue maybe
private String productCode;
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true) // according to your need
private List<Supplier> suppliers;
...
}
And,
#Entity
public class Supplier {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
...
}
#ManyToOne association:
#Entity
public class Product {
#Id
// #Column(name = "id") maybe
// #GeneratedValue maybe
private String productCode;
...
}
And,
#Entity
public class Supplier {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ManyToOne
#JoinColumn(name = "product_id", foreignKey = #ForeignKey(name = "PRODUCT_ID_FK"))
private Product product;
private String name;
...
}
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.
I'm trying to figure out what I'm doing wrong with this, but I'm learning hibernate annotations and creating a simple library system. Basically, a book gets checked out by a person, and eventually checked in. Here's how I have it configured:
#Entity
#Table
public class Book {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(nullable = false, unique = true)
private long barcode;
#Column(nullable = false)
private String name;
#OneToMany
#JoinTable(name = "checkoutsession", joinColumns = { #JoinColumn(name = "book") }, inverseJoinColumns = { #JoinColumn(name = "id")})
private List<CheckOutSession> checkOutSessions;
}
#Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column(nullable = false, unique = true)
private long barcode;
#Column(name = "firstname")
private String firstName;
#Column(name = "lastname")
private String lastName;
#OneToMany
#JoinTable(name = "checkoutsession", joinColumns = { #JoinColumn(name = "user") }, inverseJoinColumns = { #JoinColumn(name = "id")})
private List<CheckOutSession> checkOutSessions;
}
#Entity
#Table(name = "checkoutsession", uniqueConstraints = {#UniqueConstraint(columnNames={"book", "checkIn"})})
public class CheckOutSession {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#ManyToOne
#JoinColumn(name="book", nullable=false)
private Book book;
#ManyToOne
#JoinColumn(name="user", nullable=false)
private User user;
#Column(nullable = false)
private java.sql.Timestamp checkOut;
#Column
private java.sql.Timestamp checkIn;
}
I can't figure out for the life of me what I've got configured incorrectly.
[EDIT]
when I try to pull a book it is selecting everything from checkoutsession join checkoutsession join user and dies saying "Unknown column checkoutsess1_.check_in in 'field list';
[EDIT2]
A little more context, I have a BookDAO that extends JpaRepository and when I call findAll() is what's creating that query.
[EDIT3]
Rest Class:
#RestController
#RequestMapping("rest/books")
public class BookController {
#RequestMapping(method = RequestMethod.GET)
public List findBooks() {
return bookService.getAllBooks();
}
}
Service:
#Component
public class BookService {
private BookDao bookDao;
public List getAllBooks() {
return bookDao.findAll();
}
#Autowired
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
}
DAO:
public interface BookDao extends JpaRepository<Book, Long> {
}
Thanks for any help!
If I run your code and make JPA generate tables based on the entities it seems to work (at least, it does run).
However, your mappings appear to be odd to me, more specifically the #JoinTable annotation. The #JoinTable annotation is commonly used when you have a join table (eg. checkoutSession in your case), but you don't want to map it because it contains no useful information except the links between those two tables.
In that case, you use the #JoinTable annotation as following:
#ManyToMany
#JoinTable(
name = "checkoutsession", // Name of the join table
joinColumns = #JoinColumn(name = "book"), // The column name in checkoutsession that links to book
inverseJoinColumns = #JoinColumn(name = "user") // The column name in checkoutsession that links to user
)
private List<User> users;
So in this case, you can directly link the Book and User entity without having to create the CheckoutSession entity.
However, in your case your join table also contains two timestamps, if you need those in your application, then you don't have to use #JoinTable but simply use #JoinColumn to link them, for example:
#OneToMany(mappedBy = "book") // The field name in CheckoutSession that links to book
private List<CheckoutSession> checkOutSessions;
This is what you should have in your Book entity. Be aware that in this case we're talking about field names not about column names. You have to enter the name of the field in CheckoutSession that maps back to the Book entity.
For more information about the #JoinTable annotation I recommend you to read this answer: JPA "#JoinTable" annotation or this article.
Suppose, we have two entities, first one:
#Entity
#Table(name = "entitya")
public class EntityA {
#Id
#Column(name = "id")
private Long id;
#Column(name = "name")
private Long name;
#OneToMany(mappedBy = "parent", fetch = FetchType.LAZY)
private Set<EntityB> childEntities;
}
and the second:
#Entity
#Table(name = "entityb")
public class EntityB {
#Id
#Column(name = "id")
private Long id;
#Column(name = "name")
private String name;
#Column(name = "master")
private Boolean master;
#ManyToOne
#JoinColumn(name = "parent")
private EntityA parent;
}
So far, so good. However underlying database tables and constrains enforce that for any entityA there can be only one EntityB with boolean field master set to true. I can extract it by adding following method to entityA:
public entityB getMasterChild() {
for(entityB ent : childEntities) {
if(ent.isMaster()) {
return ent;
}
}
}
The question is, can I create #OneToOne relationship in EntityA that can express that rule, so that entityA can have additional masterChild member of type entityB?
If I understood you correctly you want to create/define a relationship between two entities based on a value of some entity's property. The think is that relationship between entities is defined on entities count (how many entities can has the other entity) and not on some entity's property value.
However
If you really want to use #OneToOne mapping for masterChild I would recommend creating a separate table/entity for it. Once this is done, you can include this new MasterChild entity into EntityA and annotate it with #OneToOne.
Here is new MasterChild entity
#Entity
public class MasterChild extends EntityB{
#Id
#Column(name = "id")
private Long id;
}
Note that I have removed 'master' from EntityB as it is no longer needed
#Entity
#Table(name = "entityb")
public class EntityB {
#Id
#Column(name = "id")
private Long id;
#Column(name = "name")
private String name;
#ManyToOne
#JoinColumn(name = "parent")
private EntityA parent;
}
And here is modified EntityA
#Entity
#Table(name = "entitya")
public class EntityA {
#Id
#Column(name = "id")
private Long id;
#Column(name = "name")
private Long name;
#OneToOne
private MasterChild master;
#OneToMany(mappedBy = "parent", fetch = FetchType.LAZY)
private Set<EntityB> childEntities;
}
I have two tables which have Many-to-Many relations which have a JoinTable USER_SERVICES as below.
#Entity
public class User implements Serializable {
#NotNull
#Column(unique=true)
private String username;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(
name = "USER_SERVICES",
joinColumns = {#JoinColumn(name = "id", referencedColumnName = "id")},
inverseJoinColumns = {#JoinColumn(name = "", referencedColumnName = "name")})
private Set<Services> services;
// Getters and Setters
}
#Entity
public class Services implements Serializable {
#NotNull
#GeneratedValue(strategy = GenerationType.AUTO)
#Id
private Long serviceId;
#NotNull
#Column(unique=true)
private String name;
//Getters and Setters
}
The above code creates a table USER_SERVICES, but I also want to have a Many-to-Many relation on the table USER_SERVICES with another table RATINGS which would result in another table USER_SERVICES_RATINGS. how can I define this relation with Hibernate/JPA annotations?
Bi-Directional Many to Many using user managed join table object (Relatively common)
Commonly used when you want to store extra information on the join object such as the date the relationship was created.
public class Foo{
private UUID fooId;
#OneToMany(mappedBy = "bar", cascade=CascadeType.ALL)
private List<FooBar> bars;
}
public class Bar{
private UUID barId;
#OneToMany(mappedBy = "foo", cascade=CascadeType.ALL)
private List<FooBar> foos;
}
#Entity
#Table(name="FOO_BAR")
public class FooBar{
private UUID fooBarId;
#ManyToOne
#JoinColumn(name = "fooId")
private Foo foo;
#ManyToOne
#JoinColumn(name = "barId")
private Bar bar;
//You can store other objects/fields on this table here.
}
You need to create an explicit UserServices entity and setup the relationship to the Ratings entity per your needs.
Remember that in hibernate you model relationships between entities (i.e. your java objects), not db tables.