spring data jpa fine granular auditing, custom audit - spring-data-jpa

I have requirement where I need to insert user name and group name to which the user belongs (both available in SecurityContext) in the same table.
class Entity
{
#createdBy
String username
#createdBy
String groupname
other fields ...
}
As per requirement. I cant solve this issue by making a user class and referencing it through a foreign key.
With current implementation of AuditingHandler both fields are getting the same value. How do I make sure they get respective values.
Can this be achieved using current implementation ?
If not thn how can I provide custom implementation of AuditingHandler ?

You could make a separate embeddable class and annotate it with #CreatedBy in your parent class. One way is to define a bean implementing AuditorAware, then you can make it return custom object, containing your two required fields. For example, your parent class would look like this (note the listener annotation):
#Entity
#EntityListeners(AuditingEntityListener.class)
public class AuditedEntity {
#Id
#GeneratedValue(generator = "uuid")
#GenericGenerator(name = "uuid", strategy = "uuid")
private String id;
#Embedded
#CreatedBy
private AuditorDetails createdBy;
// setters and getters
}
where AuditorDetails is:
#Embeddable
public class AuditorDetails {
private String username;
private String groupname;
// setters and getters
}
and finally, your AuditorAware bean:
#Component
class AuditorAwareImpl implements AuditorAware<AuditorDetails> {
#Override
public AuditorDetails getCurrentAuditor() {
return new AuditorDetails()
.setUsername("someUser")
.setGroupname("someGroup");
}
}
AuditingHandler fetches your custom AuditorDetails from your AuditorAware bean (it must be single bean implementing it) and sets it in your auditable entity.

Related

How can I share an Entity for other entities' OneToMany

I have an Entity look like this.
#Entity
class Property extends BaseEntity {
#Basic
private String name;
#Basic
private String value;
}
The basic intention is using this Entity as other Entities properties.
#Entity
class MyEntity extends BaseEntity {
#OneToMany
private List<Property> properties;
}
#Entity
class YourEntity extends BaseEntity {
#OneToMany
private List<Property> properties;
}
How can I do this? Do I have to define each owner's field in Property?
#Entity
class Property extends BaseEntity {
#Basic
private String name;
#Basic
private String value;
#ManyToOne(optional = true)
private MyEntity myEntity;
#ManyToOne(optional = true)
private YourEntity yourEntity;
#ManyToOne(optional = true)
private OtherEntity otherEntity;
}
Basically it is a good solution You represented here. There is the option to create a join table which will help you to keep the entity "cleaner" (and also could be used as a ManyToMany. In most of the cases I prefer to use the option You provided [simplicity is a gooooood thing :) ], but other colleagues got different view on this problem.
TL.DR: Your provided code is working and I personally prefer it. There are other ways but those are a bit slower etc.

How can I set foriegn key column in Spring JPA

I am using Spring JPA and want to set value to a foreign key column. Here is my entities and repository.
#Entity
public class Device {
#NotEmpty
#Id
private String deviceId;
#ManyToOne
#JoinColumn(name="userId", referencedColumnName="userId", insertable=false, updatable=false)
#NotFound(action=NotFoundAction.IGNORE)
private User user;
//Getters and setters
}
#Entity
public class User(){
#Id
private String userId;
private String userName;
//Getters and setters
}
public interface DeviceRepository extends PagingAndSortingRepository {
}
public class DeviceServiceImpl implements DeviceService {
#Autowired
private DeviceRepository devRepos;
#Autowired
private UserRepository userRepos;
#Override
public void saveDevice(Device device, String userId) {
User user = null;
if (userId!=null) {
user = userRepos.findOne(userid);
device.setUser(user);
}
deviceRepos.save(device);
}
}
The user exists in Device table but userId column in the table does not set the value. Please help me to fix the problem.
EDIT:
I removed insertable and updatable from the annotation and now it works.
#JoinColumn(name="userId", referencedColumnName="userId")
Then, this means I have to get user of the device from the User table whenever I save a device?
Because you set insertable and updatable to false for the user property in your Device class , it will cause the persistence provider to ignore this column (Device.userId) when generating SQL INSERT and UPDATE statement.
Just change them to true or remove them as their default values are already true.
Update :
this means I have to get user of the device from the User table
whenever I save a device?
In pure JPA , if you know the ID of the user , you can use EntityManager#getReference(User.class , aUserId) to get an User instance without actually querying from DB . But in Spring Data JPA , it seems that this method is not supported out of the box.

JPA / Hibernate OneToMany Mapping, using a composite PrimaryKey

I'm currently struggling with the right mapping annotations for a scenario using a composite Primary Key Class. First, what I am trying to achieve in words:
I have 2 classes: group and FieldAccessRule. A Group can have many FieldAccessRules, while a FieldAccessRule only has ONE Group assigned. Modling this is not a problem so far (simplified):
public class Group{
...
#OneToMany(mappedBy = "group")
private Set<FieldAccessRule> fieldAccessRules;
...
}
and for the FieldAccessRule:
public class FieldAccessRule {
...
#ManyToOne
#JoinColumn(name = "group_id")
private Group group;
...
}
Now, I decided to use a Composite PK for the FieldAccessRule, because a Rule should be unique for ONE Group and ONE Field. It looks like this:
#Embeddable
public class FieldAccessRulePK implements Serializable{
private String fieldKey;
private Group group;
...
}
And ofc. the FieldAccessRule needs to change to
public class FieldAccessRule {
...
#EmbeddedId
private FieldAccessRulePK fieldAccessRulePK;
...
}
How do I create the right Mapping for the FieldAccessRuleSet of Group now?
Doing it like this, I get :
In attribute 'fieldAccessRules', the "mapped by" value 'group' cannot be resolved to an >attribute on the target entity.
Whats the right way of creating the mapping from Group to A PART of the PrimaryKey?
Edit:
I know found out, that using
public class Group{
...
#OneToMany(mappedBy = "fieldAccessRolePK.group")
private Set<FieldAccessRule> fieldAccessRules;
...
}
is EXACTLY working as expected. It compiles fine, it creates the DB fine and after loading a group with predefined Roles, they are available as expected.
However, Eclipse still says
In attribute 'fieldAccessRules', the "mapped by" value 'fieldAccessRulePK.group' cannot be resolved to an attribute on the target entity.
Im not sure, if it's good to ignore Error and "assume" everythings fine... (I found a post, where it has been said, that a mapping of the pattern attr1.attr2 is supported by Hibernate but not JPA-confirm.)
In your code, EntityManager cannot resolve mappedBy attribute fieldAccessRulePK.group.
Reason
EntityManager assume that FieldAccessRule entity have an attribute name with fieldAccessRulePK.group during the FieldInjection.
According to Java Naming Variables Rule, you cannot name fieldAccessRulePK.group by using characters dot > '.'
Java Naming Variables Rule Reference
All variable names must begin with a letter of the alphabet, an underscore ( _ ), or a dollar sign ($). The rest of the characters may be any of those previously mentioned plus the digits 0-9.
The convention is to always use a letter of the alphabet. The dollar sign and the underscore are discouraged.
One more thing:
Don't use group instance in your FieldAccessRulePK embeddable class. For more reference here.
Try as below :
#Embeddable
public class FieldAccessRulePK implements Serializable{
#Column(name = "FIELD_KEY")
private String fieldKey;
#Column(name = "GROUP_ID")
private String groupId;
}
public class FieldAccessRule {
#EmbeddedId
private FieldAccessRulePK id;
#ManyToOne
#JoinColumn(name = "GROUP_ID", referencedColumnName = "ID")
private Group group;
}
public class Group{
#OneToMany(mappedBy = "group")
private Set<FieldAccessRule> fieldAccessRules;
}
I've fixed a similar problem by modifying the mappedBy attribute of the parent class (using dotted notation)
public class Group{
...
#OneToMany(mappedBy = "fieldAccessRulePK.group")
private Set<FieldAccessRule> fieldAccessRules;
...
}
and by keeping the annotations in the PK class:
#Embeddable
public class FieldAccessRulePK implements Serializable{
private String fieldKey;
#ManyToOne
#JoinColumn(name = "group_id")
private Group group;
...
}

Single Table Inheritance Query

i have an existing table for TransactionLogs which is either links to a External or to a InternalType. the id's corresponding to the cash adjustment & game transaction are stored in a single column called transaction id and a separate column called type indicates which table is it linked to
Because of the nature of the existing table, i mapped it in a single table inheritance:
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "TYPE", discriminatorType = DiscriminatorType.INTEGER)
public class TransLog implements Serializable {
#Id
#GeneratedValue
private Long id;
private Integer type;
// getters and setters
}
#Entity
public class InternalAdjustmentTransLog extends TransLog {
#ManyToOne
#JoinColumn(name = "TransID", nullable = false)
private InternalAdjustmentRecord internalAdjustmentRecord;
// getters and setters
}
#Entity
public class ExternalTransLog extends TransLog {
#ManyToOne
#JoinColumn(name = "TransID", nullable = false)
private ExternalAdjustmentRecord externalAdjustmentRecord;
}
each of these two subclasses has their subclasses with defined descriminator values..
With the setup given above, there are instances that i need to get a unified data of both
internal and external records. What is the best way to accomplish this? at first i thought it would be enough to use the TransLog as the root class for the query (i'm using jpa criteria). however, i need to get TransId (which are defined in the subclasses and points to 2 different objects of no relationship).
Thanks.
You can make abstract method in TransLog that returns what you need and implement it in both subclasses.

Why JPA-2.0 Primary Key Classes have to implement Serializable but my example works without?

In many sources I have read PrimaryKey Classes and even JPA2 entities should be serializable.
IN my example (legacy database) there is a relationship between employee and languages:
Employee Class:
#Entity
#IdClass(EmpleadoId.class)
#Table(name = "NO_INFGRAEMPL")
public class Empleado {
#Id
#Column(name = "IGECOMPANIA", unique = true)
private String compania;
#Id
#Column(name = "IGENUMEROIDENTIFIC", unique = true)
private String numeroIdentificacion;
//...
}
Employee Compound PrimaryKey Class:
public class EmpleadoId {
private String compania;
private String numeroIdentificacion;
//...
}
Employee Language SKill Class:
#Entity
#IdClass(IdiomaEmpleadoId.class)
#Table(name = "NO_IDIOMEMPLE")
public class IdiomaEmpleado {
#Id
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumns(value = {
#JoinColumn(name= "IEMCOMPANIA", referencedColumnName = "IGECOMPANIA"),
#JoinColumn(name = "IEMEMPLEADO", referencedColumnName = "IGENUMEROIDENTIFIC")
})
private Empleado empleado;
#Id
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "IEMIDIOMA")
private Idioma idioma;
#Column(name = "IEMNIVELLECTURA")
private String nivelLectura;
//...
}
Employee Language Skill Compound PrimaryKey Class:
public class IdiomaEmpleadoId {
private EmpleadoId empleado;
private String idioma;
//...
}
Language Class:
#Entity
#Table(name = "NO_IDIOMAS")
public class Idioma {
#Id
#Column(name = "IDICODIGO")
private String codigo;
#Column(name = "IDIDESCRIPCION")
private String descripcion;
//...
}
I am using EclipseLink JPA2 Provider under a J2SE application and it is not giving me any exceptions.
My questions are:
Why is it not giving me exceptions? Is it not enforced to have Serializable?
Is it safe to continue this way or should I definitely implemente serializable?.
In which ones?, JPA2 Entities or PrimaryKey Classes?
Thanks a lot for the help.
JPA specification contains such a requirement (JSR-317 secion 2.4 Primary Keys and Entity Identity):
The primary key class must be serializable.
If EclipseLink really doesn't enforce this requirement, it's an implementation detail of EclipseLink and I wouldn't recommend you to rely on it.
However, there are no requirements on serializability of entities, except for the following one which looks more like a recommendation than a requirement:
If an entity instance is to be passed by value as a detached object (e.g., through a remote interface), the
entity class must implement the Serializable interface.
Nothing is required to be serializable, but it seems it is requried by the spec (10x to axtavt) for primary keys, although there is no direct need for it.
Serialization is needed if the objects are transferred over-the-wire or persisted to disk, so I can't see the reason behind that decision. However, you should conform to it.
Primary key classes have to implement serializable and composite-ID class must implement serializable are two different questions.
I am going to answer you both, and hope it will help you to distinguish and understand holistically.
Primary key classes have to implement serializable:
Note: It could work without its iplementation also.
JPA specification contains such a requirement (JSR-317 secion 2.4 Primary Keys and Entity Identity):
The primary key class must be serializable.
However, there are no requirements on serializability of entities, so it's a recommendation than a requirement
exception:
If an entity instance is to be passed by value as a detached object (e.g., through a remote interface), the entity class must implement the Serializable interface.
Composite-ID class must implement serializable.
The id is used as a key to index loaded objects in the session.
The session object needs to be serializable, hence all objects referenced by it must be serializable as well.
In case of CompositeIds the class itself is used as the id.