#IdClass JPA Annotation - jpa

Here is a composite primary key for one of my entities.
public class GroupMembershipPK implements Serializable{
private static final long serialVersionUID = 7816433876820743311L;
private User user;
private Group group;
public GroupMembershipPK(){
}
public boolean equals(Object o){
if(o==null){
return false;
}
if(o instanceof GroupMembershipPK){
final GroupMembershipPK groupMembershipPK=(GroupMembershipPK)o;
return groupMembershipPK.group.getGroupName().equals(this.group.getGroupName()) &&
groupMembershipPK.user.getName().equals(this.user.getName());
}
return false;
}
public int hashCode(){
return super.hashCode();
}
}
Heres my entity(part of) using the above as a Composite Primary Key.
#Entity
#IdClass(GroupMembershipPK.class)
public class GroupMembership extends AbstractModelElementVersionOther{
private static final long serialVersionUID = 9188465566607060376L;
private String memType;
private Group group;
private User user;
public GroupMembership(){
super();
}
#Column(nullable=false)
public String getMemType(){
return this.memType;
}
public void setMemType(String memType){
this.memType=memType;
}
#Id
#ManyToOne
#JoinColumn(name="groupId")
public Group getGroup(){
return this.group;
}
public void setGroup(Group group){
this.group=group;
}
#Id
#ManyToOne
#JoinColumn(name="userId")
public User getUser(){
return this.user;
}
public void setUser(User user){
this.user=user;
}
#Override
public boolean equals(Object o) {
//
I am a little confused on what should be the equals method implementation for the above entity. How can I compare two composite primary keys?
Any comments on other parts of my code are welcome too.

It is not a good idea you store entities as primary key. There is some limitations when using query language and JPA 1.0 does not support. Besides it there is no need to use entities as primary key. Think about it.If you want, take a special look at the following question
A class that behaves like #Entity and #Embeddable
Answer one
Comment about answer one
You will see that using a entity as primary key is not necessary.
Instead of
public class GroupMembershipPK implements Serializable {
private User user;
private Group group;
}
Use
public class GroupMembershipPK implements Serializable {
private Integer userId;
private Integer groupId;
}
equals implementation is important because JPA compares two entities by using it (JPA checks whether an entity is in persistence context by using equals implementation). So you can implement according to
public boolean equals(Object o) {
if(o == null)
return false;
if(!(o instanceof GroupMembershipPK))
return false;
GroupMembershipPK other = (GroupMembershipPK) o;
if(!(getUserId().equals(other.getUserId()))
return false;
if(!(getGroupId().equals(other.getGroupId()))
return false;
return true;
}
Advice: it is a good idea you use property access instead of field access because, at some times, JPA implementation uses a proxy object because of performance issues. A proxy object makes use of property access because it allows JPA implementation hits the database when necessary.
How to save an object that uses a composite primary key ?
User user = new user();
Group group = new Group();
entityManager.save(user);
entityManager.save(group);
entityManager.flush();
UserGroup userGroup = new UserGroup();
userGroup.setId(new UserGroup.UserGroupId(user.getId(), group.getId()));
entityManager.save(userGroup);
Do you want to know how to implement UserGroup ?
public class UserGroup {
private UserGroupId id;
// You can create UserGroupId outside UserGroup class
// Feel free to choice your best approach
#Embeddable
public static class UserGroupId implements Serializable {
private Integer userId;
private Integer groupId;
// required no-arg constructor
public UserGroupId() {}
public UserGroupId(Integer userId, Integer groupId) {
this.userId = userId;
this.roupId = groupId;
}
// getter's and setter's
// equals and hashcode as shown above
}
#EmbeddedId
public UserGroupId getId() {
return this.id;
}
public setId(UserGroupId id) {
this.id = id;
}
}
Another approach to use composite primary key is IdClass. See IdClass
regards,

It was mentioned partially, anyway:
When implementing the equals method, you should use instanceof to allow comparing with subclasses. If Hibernate lazy loads a one to one or many to one relation, you will have a proxy for the class instead of the plain class. A proxy is a subclass. Comparing the class names would fail.
More technically, you should follow the Liskovs Substitution Principle and ignore symmetry.
The next pitfall is using something like name.equals(that.name) instead of name.equals(that.getName()). The first will fail, if that is a proxy.
http://www.laliluna.de/jpa-hibernate-guide/ch06s06.html

Related

Will JPA generate more than one auto generated coulmns

everyone I have a requirement that I want to generate two auto-generated values for two different columns. I am using Azure SQL DB as my RDBMS.
and I am using spring data JPA to persist my values.
Example:
#Entity
#Table(name="T_JUST_FOR_TEST")
public class TJustForTest implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#SequenceGenerator(name="SEQ_GEN", sequenceName="SEQ_JUST_FOR_TEST", allocationSize=1)
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="SEQ_GEN")
private long id;
private String userRegistrationId;
public TJustForTest() {}
public long getId() {
return this.id;
}
public void setId(long id) {
this.id = id;
}
}
here, I would like to use Id as my primary key as auto-generated and userRegistration Id also auto-generated but not primary key just a unique key and some custom format
Example
I will append some string as prefix and id as a suffix, meaning I will use the same primary key to generate the userRegistration No.
is there any way to achieve this or any other way around it, please clarify.
finally, I found a better solution to generate userRegistration by using Id,
I need to write one Listener class to get the auto-generated Id in my using #PostPersist
annotation, actually this will be called once the entity object persists in DB.
public class TJustForTestListener {
#PostPersist
public void getPostPersist(TJustForTest ob) {
try {
ob.setuserRegistrationId("CR"+ob.getId());
}catch(Exception e) {
e.printStackTrace();
}
}
and in Entity level i need to declare my listener class by using #EntityListeners
#Entity
#Table(name="T_JUST_FOR_TEST")
#EntityListeners(TJustForTestListener .class)
public class TJustForTest implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#SequenceGenerator(name="SEQ_GEN", sequenceName="SEQ_JUST_FOR_TEST", allocationSize=1)
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="SEQ_GEN")
private long id;
private String userRegistrationId;
public TJustForTest() {}
public long getId() {
return this.id;
}
public void setId(long id) {
this.id = id;
}
}
that's all it is required, JPA will insert the record first then update automatically.

jpa bidirectional relationship binding error

I have two entities, a State and a Location. A State can have many locations.
This mapping should be pretty straightforward.
#Entity
#Table(name="Locations")
public class Location {
public Location() {};
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private int id;
#ManyToOne
#JoinColumn(name="StateCode")
private State state;
}
and the State class:
#Entity
#Table(name="States")
public class State {
private static final Logger log = LoggerFactory.getLogger(State.class);
public State() {
locations = new ArrayList<Location>();
};
#OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER, mappedBy="location")
private Collection<Location> locations;
#Id
#Column(name="StateCode", nullable=false, length=6)
private String stateCode;
public Collection<Location> getLocations() {
return locations;
}
public void setLocations( Collection<Location> locations ) {
this.locations = locations;
}
public String getStateCode() {
return stateCode;
}
public void setStateCode(String code) {
this.stateCode = code;
}
and yet I get this error:
AnnotationException mappedBy reference an unknown target entity property: net.rallaesystems.vibracheck.model.Location.location in net.rallaesystems.vibracheck.model.State.locations
I have followed the advice of changing the mapping to the 'state' field. With this code, though
#Repository
public interface StateRepository extends CrudRepository<State, String>{
List<State> findAll();
}
being called in a simple controller, the serialization of States goes into an infinite loop. Maybe there is a problem with the serialization library? I think it is Jackson.
You should update the #OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER, mappedBy="location") to #OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER, mappedBy="state"), this indicate the state is the owner
where the state is present in your Location class
#ManyToOne
#JoinColumn(name="StateCode")
private State state;
Yes, as somebody answered, you need use a valid property of the Location which is a foreign key on the location table (here its state property of location class, and statecode is foreign key in the location table).

Disable additional criteria only in some entity relations

I am making an application based on JPA/EclipseLink, I implemented a soft delete functionality using #AdditionalCriteria in the root class of the hierarchy of entities (all entities inherit from this).
My problem is that now, I need to create a special entity that contains multiple relationships with other entities; and I need recover all relationed entities, including soft deleted ones. It is possible disabled #AdditionalCriteria only in the relations of this special entity with EclipseLink? If not, what is the best option to do this? My code looks like the following:
////Example of my top entity class (all others inherit from this)
#AdditionalCriteria("this.deleted = false")
public abstract class TopHierarchyClass {
···
#Column(name = "deleted")
protected boolean deleted = false;
···
}
//Example of entity that needs recover all relationed entities including soft deleted
#Entity
#Table(name = "special_entities")
public class SpecialEntity extends EntityBase {
···
#JoinColumn(name = "iditem", referencedColumnName = "id")
#ManyToOne(fetch = FetchType.LAZY)
private Item item;
···
}
#Entity
#Table(name = "items")
public class Item extends EntityBase {
···
}
Thanks in advance
Create a new entity for the same table without the #AdditionalCriteria. This way you can retrieve all records from that table without applying the additional filter.
For example:
public interface Person {
Long getId();
void setId(Long id);
Date getDeletedAt();
void setDeletedAt(Date deletedAt);
}
#MappedSupperclass
public class PersonEntity implements Person {
#Id
private Long id;
private Date deletedAt;
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public Date getDeletedAt() { return deletedAt; }
public void setDeletedAt(Date deletedAt) { this.deletedAt = deletedAt; }
}
#Entity
#Table(name = "people")
#AdditionalCriteria("this.deletedAt is null")
public class ActivePersonEntity extends PersonEntity {
}
#Entity
#Table(name = "people")
public class RawPersonEntity extends PersonEntity {
}
public class PeopleRepository {
#PersistenceContext
private EntityManager em;
public List<Person> all() {
return all(false);
}
public List<Person> all(boolean includeSoftDeleted) {
if (!includeSoftDeleted) {
return em.createQuery("select p from ActivePersonEntity p", ActivePersonEntity.class).getResultList();
} else {
return em.createQuery("select p from RawPersonEntity p", RawPersonEntity.class).getResultList();
}
}
}
Also, if your #AdditionalCriteria is in a super class, you may override it by declaring a new empty #AdditionalCriteria in a sub class:
You can define additional criteria on entities or mapped superclass.
When specified at the mapped superclass level, the additional criteria
definition applies to all inheriting entities, unless those entities
define their own additional criteria, in which case those defined for
the mapped superclass are ignored.
#AdditionalCriteria doc

JPA One-to-Many relationship using a List - OrderBy ignored/not working

I'll try to formulate the question more simple:
#Entity
public class One implements Serializable {
...
#Id
#GeneratedValue
private Long id;
#OneToMany
#OrderBy("name ASC")
private List<Many> many;
...
First I populate the List with some Many-Entities and persist the One-Entity. Second I retrieve (em.find) the One-Entity expecting the List in ascending order by Many#name, but it's not ordered by name. The List is ordered by id. Complete code see below if necessary.
Original post some days ago:
I'm using a current Netbeans Glassfish bundle.
Product Version: NetBeans IDE 8.0 (Build 201403101706)
Updates: NetBeans IDE is updated to version NetBeans 8.0 Patch 2
Java: 1.7.0_51; Java HotSpot(TM) 64-Bit Server VM 24.51-b03
Runtime: Java(TM) SE Runtime Environment 1.7.0_51-b13
System: Mac OS X version 10.9.3 running on x86_64; UTF-8; de_DE (nb)
The JPA #OrderBy annotation is completely ignored.
#Entity
public class One implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue
private Long id;
#OneToMany
#OrderBy("name ASC")
private List<Many> many;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public List<Many> getMany() {
return many;
}
public void setMany(List<Many> many) {
this.many = many;
}
}
The many Entity
#Entity
public class Many implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue
private Long id;
private String name;
public Many() {
}
public Many(String name) {
this.name = name;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
The service class (EJB)
#Stateless
public class Service {
#PersistenceContext(unitName = "cwPU")
private EntityManager em;
public One createOne() {
return em.merge(new One());
}
public Many createMany(String name) {
return em.merge(new Many(name));
}
public One add(Long oneId, Long manyId) {
One one = em.find(One.class, oneId);
Many many = em.find(Many.class, manyId);
one.getMany().add(many);
return one;
}
public One find(Long id) {
One one = em.find(One.class, id);
return one;
}
}
The main class
public class Main {
public static void main(String[] args) throws NamingException {
EJBContainer container = EJBContainer.createEJBContainer();
Context ctx = container.getContext();
Service service = (Service) ctx.lookup("java:global/classes/Service");
One one = service.createOne();
Many many = service.createMany("the-first");
service.add(one.getId(), many.getId());
many = service.createMany("a-second");
one = service.add(one.getId(), many.getId());
one = service.find(one.getId());
System.out.println("-------------------------------------------------");
for (Many m : one.getMany()) {
System.out.println(m.getName());
}
container.close();
}
}
The output:
the-first
a-second
No matter what I write to the #OrderBy annotation (name ASC, name DESC, id ASC, id DESC), the output is always the same ascending order by the id.
Any idea what I'm missing?
The #Orderby annotation doesn't actually work that way. According to the javadoc, the annotation "Specifies the ordering of the elements of a collection ...at the point when the collection is retrieved."
So the annotation affects the result of the query (find), but does not dictate the order in the collection you store the result set into.
The solution is calling em.refresh (at the right place) as stated from Chris and WPrecht. I had to do this in a separate EJB method.
This did not work:
public One find(Long id) {
em.refresh(em.find(One.class, id)); // did not work
One one = em.find(One.class, id);
return one;
}
Adding a separate refresh method
public void refresh(Long id) {
em.refresh(em.find(One.class, id));
}
and calling it in the main program
...
service.refresh(one.getId());
one = service.find(one.getId());
...
works!
Probably I have to do more reading to understand caching.

JPA Mapping, parent to children and child to parent with two classes and abstract class

I have have two classes that inherit from an abstract class and have a parent-children relation.
So I use annotation OneToMany and ManyToOne but the parent entity in child class is always null.
Can Someone help me please, I have spend several hours to googling and test many conf without success.
These are code from my classes :
public #Table(name="flowentity") #Entity abstract class FlowEntity {
final static Logger log = LoggerFactory.getLogger(FlowEntity.class);
//Globals informations concerning the flow state
private #Id #GeneratedValue(strategy = GenerationType.IDENTITY) Integer flowId = 0;
private String flowName;
private #OneToMany(fetch=FetchType.EAGER, cascade=CascadeType.ALL)
Set<PeopleEntity> actorSet = new HashSet<>();
//Global parameters for most of flows
//Organizational parameters
private #OneToOne(fetch=FetchType.EAGER, cascade=CascadeType.ALL)
#JoinColumn(name="organisationalEntity_Id")
OrganisationalEntity organisationalEntity;
...
public #Table(name="ams_newCPEntity") #Entity class NewMultiCPEntity extends FlowEntity {
private #OneToMany(targetEntity=NewCPEntity.class, fetch=FetchType.EAGER, cascade=CascadeType.ALL,mappedBy="parent")
Set<NewCPEntity> cpList = new HashSet<NewCPEntity>();
//Constructor
public NewMultiCPEntity(){
setFlowName(EnumFlow.N_CP_M.getFlowAcronym());
}
...
public #Table(name="ams_newCPEntity") #Entity class NewCPEntity extends FlowEntity {
final static Logger log = LoggerFactory.getLogger(NewCPEntity.class);
private boolean formNCPValidated;
private #ManyToOne #JoinColumn(name="parent_Id", nullable=false)
NewMultiCPEntity parent;
public NewCPEntity(){
log.debug("Instanciation of a new CP");
setFlowName(EnumFlow.N_CP.getFlowAcronym());
}
public #Override OrganisationalEntity getOrganisationalEntity(){
return parent.getOrganisationalEntity();
}
...
If I don't add the #JoinColumn annotation, JPA create an association table but is not able to retrieve the parent whereas the association can be done directly by requesting in database.
Thankyou very much to help.
Regards,
Thank you Chris for your comment, you are right, I forget to change the name of the table. I don't think it was the problem because the inheritance mapping is in one table flowentity with a DTYPE discriminator column.
Finally I resolve my problem by setting parent attributs when adding a new child like this :
public #Table #Entity class NewMultiCPEntity extends FlowEntity {
private #OneToMany(targetEntity=NewCPEntity.class, fetch=FetchType.EAGER, cascade=CascadeType.ALL)
List<NewCPEntity> cpList = new ArrayList<>();
//Constructor
public NewMultiCPEntity(){
setOrganisationalEntity(new OrganisationalEntity());
setFlowName(EnumFlow.N_CP_M.getFlowAcronym());
}
public List<NewCPEntity> getNCPList(){
if(cpList == null){
cpList = new ArrayList<>();
}
if(cpList.isEmpty()){
addCPEntity(new NewCPEntity());
}
return Collections.unmodifiableList(cpList);}
public boolean removeCPEntity(NewCPEntity entity){
return cpList.remove(entity);
}
public boolean addCPEntity(NewCPEntity entity){
entity.setParent(this);
entity.setOrganisationalEntity(this.getOrganisationalEntity());
return cpList.add(entity);
}
And I remove the override of getOrganizationalEntity in the child :
public #Table #Entity class NewCPEntity extends FlowEntity {
final static Logger log = LoggerFactory.getLogger(NewCPEntity.class);
private #ManyToOne(targetEntity=NewMultiCPEntity.class,cascade=CascadeType.ALL)
NewMultiCPEntity parent;
public NewCPEntity(){
log.debug("Instanciation of a new CP");
setFlowName(EnumFlow.N_CP.getFlowAcronym());
}
public NewMultiCPEntity getParent() {
return parent;
}
public void setParent(NewMultiCPEntity parent){
this.parent = parent;
}
Regards,