Child entity with Secondary Table and Compound Key spread over two tables - jpa

After much debate on choosing an approach for an internationalized database design I went with having two tables for each table that requires translation. I'm having some trouble with ORM in the following case.
So I have the following tables:
cat cat_t subcat subcat_t
------ ------- ---------- ------------
id (pk) cat_id(pk,fk) id(pk) subcat_id(pk,fk)
locale(pk) cat_id(fk) locale(pk)
name name
#Entity
#Table(name = "cat")
#SecondaryTable(name = "cat_t",
pkJoinColumns = #PrimaryKeyJoinColumn(name = "cat_id",
referencedColumnName = "id"))
#IdClass(TranslationKey.class)
public class Category {
#Id
private long id;
#Id
#Column(table = "cat_t")
private String locale;
#Column(table = "cat_t")
private String name;
#OneToMany(fetch = FetchType.LAZY)
private List<SubCategory> subCategories;
// getters and setters
}
#Entity
#Table(name = "subcat")
#SecondaryTable(name = "subcat_t",
pkJoinColumns = #PrimaryKeyJoinColumn(name = "subcat_id",
referencedColumnName = "id"))
#IdClass(TranslationKey.class)
public class SubCategory{
#Id
private long id;
#Id
#Column(table = "subcat_t")
private String locale;
#Column(table = "subcat_t")
private String name;
#Column(name = "cat_id")
private long categoryId;
// getters and setters
}
public class TranslationKey implements Serializable {
private long id;
private String locale;
// getters and setters
}
My goal is for subcategories to only pull back the subcategories for the locale of the parent. I think I have some options including, querying the subcategories separately and making the field transient or pull everything back (all subategories for all languages) and then just filter out the ones I want.
The issue I have had with #JoinColumn is that locale is part of the secondary table for both cat can subcat and so when I try the referencedColumn that may not be allowed since its not in the same table?. I'm using EclipseLink but I'm not really tied to a JPA Provider.
Any help/guidance is much appreciated

cat seems to have a one to many relationship with cat_t, since there can be many rows in cat_t for a single cat entry. This is not ideal to have in a single Entity, since it means you will have instances sharing data, and endless headaches if you make changes.
A better approach that makes updates possible is to map the cat and cat_t to separate Entities, where Cat has a collection of Cat_t?Local instances. The entity mapping to cat_t can use the local and its ManyToOne back pointer to Cat as its Id:
#Entity
#IdClass(TranslationKey.class)
public class Local {
#ID
private String locale;
#ID
#ManyToOne
private Cat cat;
}
public class TranslationKey{
string local;
long cat;
}

Related

JPA how to set foreign id - in Embeddable id - that is generated

My use case is: I have a Product with multi language name. To have at most one name per language a translation should be identified by locale + productId.
My problem is to get it working with a generated productId. This are the entities:
e.g. in oracle i get: "ORA-01400: cannot insert null into ."PRODUCT_NAME"."FOREIGN_ID""
Product:
#Entity
#Data
public class Product {
#Id
#GeneratedValue
private Long id;
#Column(unique = true)
private String articleNumber;
/**
* Localized name of the product.
*/
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
#MapKey(name = "nameId.locale")
private Map<Locale, ProductName> names = new HashMap<>();
}
ProductName:
#Entity
#Data
public class ProductName {
#EmbeddedId
private TranslationId nameId;
private String name;
}
TranslationId:
#Embeddable
#Data
public class TranslationId implements Serializable {
private static final long serialVersionUID = 3709634245257481449L;
#Convert(converter = LocaleConverter.class)
private Locale locale;
private Long foreignId; //<-- this is null, should reference product
}
Is there a way to get this working without having to save the product first without the name? Without a generated id it is working of course - i just set same id for both.
I would like to re-use the translation id for other translated fields of other entities.

Creating new JPA Entity with EmbeddedID

I have a very simple set of tables modelling a many to many relationship.
Foo -< FooBar >- Bar
Which is working fine when I select data but when I try to create a new instance of the relationship I can getting errors because JPA is trying to insert nulls.
I am having to set both the #ManyToOne values as well as the key (which represent the same things) and this makes me think this is not setup correctly.
Question is therefore how do I correctly setup the annotations to create a new FooBar relationship?
Entities
#Entity
#Table(name = "FOO")
public class Foo implements Serializable {
#Id
#Column(name = "FOO_ID")
private int id;
#column(name = "FOO_DESC")
private String desc;
#OneToMany(mappedBy = "foo")
private List<FooBar> fooBars;
//getters / setters / hashCode / equals
}
#Entity
#Table(name = "BAR")
public class Bar implements Serializable {
#Id
#Column(name = "BAR_ID")
private int id;
#column(name = "BAR_DESC")
private String desc;
#OneToMany(mappedBy = "bar")
private List<FooBar> fooBars;
//getters / setters / hashCode / equals
}
Intersection Table (and Key)
#Embeddable
public class FooBarKey implements Serializable {
private int fooId;
private int BarId;
//getters / setters / hashCode / equals
}
#Entity
#Table(name ="FOO_BAR_XREF")
public class FooBar implements Serializable {
#EmbeddedId
private FooBarKey key;
#MapsId("fooId")
#ManyToOne
#JoinColumn(name = "FOO_ID", referencedColumnName = "FOO_ID")
private Foo foo;
#MapsId("barId")
#ManyToOne
#JoinColumn(name = "BAR_ID", referencedColumnName = "BAR_ID")
private Bar bar;
#Column(name = "DESC")
private String desc;
//getters / setters / hashCode / equals
}
Creating Relationship
Finally I am creating a new intersection instance:
//foo and bar exist and are populated
FooBar fb = new FooBar();
FooBarKey fbk = new FooBarKey();
fbk.setFooId(foo.getId());
fbk.setBarId(bar.getId());
fb.setKey(fbk);
fb.setDesc("Some Random Text");
entityManager.persist(fb);
at which point JPA errors with the insert saying it cannot insert null into FOO_ID.
I have checked and at the point I persist the object, the key is populated with both Foo and Bar IDs.
If I add
fb.setFoo(foo);
fb.setBar(bar);
prior to the persist it works but should the #MapsId not effectively tell JPA to map using the key?
I presume that I should be setting both the #ManyToOne and key values which are logically the same thing so I must have something not configured correctly?
I am using Eclipselink if that makes a difference.
When you use mapsId, you are telling JPA that this relationship, and the value of the target entity's primary key, is used to set the mapping named within the mapsId value. JPA then uses this relationship mapping to set the foreign key AND the basic mapping value when it flushes or commits. In your case, you left the relationship NULL, which forces the FK to be null when it gets inserted.
This allows sequencing to be delayed, as you may not have the primary key generated in the referenced entity when creating and traversing the graph - JPA will calculate and populate the values and propagate them through the graph when it needs them.
If you aren't using the ID class in your model, the simplest solution is just to remove it and avoid the overhead:
#Entity
#IdClass(package.FooBarKey.class)
#Table(name ="FOO_BAR_XREF")
public class FooBar implements Serializable {
#Id
#ManyToOne
#JoinColumn(name = "FOO_ID", referencedColumnName = "FOO_ID")
private Foo foo;
#Id
#ManyToOne
#JoinColumn(name = "BAR_ID", referencedColumnName = "BAR_ID")
private Bar bar;
#Column(name = "DESC")
private String desc;
//getters / setters / hashCode / equals
}
public class FooBarKey implements Serializable {
private int foo;
private int bar;
}
IdClass has similar restrictions to what is needed for #EmbeddedId but with one more - the names within it must match the property names designated with #Id, but the types must be the same as the ID class within the referenced entity. Pretty easy if you are using basic mappings within Foo and Bar, but can be more complex.
Adding more to your composite key is easy:
#Entity
#IdClass(package.FooBarKey.class)
#Table(name ="FOO_BAR_XREF")
public class FooBar implements Serializable {
#Id
#ManyToOne
#JoinColumn(name = "FOO_ID", referencedColumnName = "FOO_ID")
private Foo foo;
#Id
#ManyToOne
#JoinColumn(name = "BAR_ID", referencedColumnName = "BAR_ID")
private Bar bar;
#Id
private int someValue
#Column(name = "DESC")
private String desc;
//getters / setters / hashCode / equals
}
public class FooBarKey implements Serializable {
private int foo;
private int bar;
private int someValue
}
JPA will populate your foreign keys for you when the relationships are not-null, but any other fields require either sequencing or your own mechanisms to ensure they are populated prior to insert, and all Ids should be treated as immutable within JPA.

How to show 2 different type of mappings between 2 entity classes, holding each other's reference

The mappings between the 2 tables(Department and Employee) is as follows (Link for the image showing mapping is also provided):
Every department has one and only one department head.
Every department can have more than one employee.
dept_id and empId are primary keys of their respective tables.
dept_head(It is the Employee Id) and dept are foreign keys of their
respective tables.
Mapping Employee and Department table
I created entity classes for the above 2 tables (The structure is provided below).
Employee Class:
public class Employee implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name = "empId")
private Integer empId;
#Size(max = 45)
#Column(name = "name")
private String name;
#Size(max = 45)
#Column(name = "address")
private String address;
#Size(max = 45)
#Column(name = "grade")
private String grade;
#Size(max = 45)
#Column(name = "email")
private String email;
#JoinColumn(name = "dept", referencedColumnName = "dept_id")
#ManyToOne
private Department deptartment;
.. ...
}
Department class:
public class Department implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Basic(optional = false)
#NotNull
#Size(min = 1, max = 8)
#Column(name = "dept_id")
private String deptId;
#Size(max = 45)
#Column(name = "name")
private String name;
#JoinColumn(name = "dept_head", referencedColumnName = "empId")
#OneToOne
private Employee deptHead;
#OneToMany(mappedBy = "deptartment")
private List<Employee> employeeList;
....
...
}
If I am adding mappedBy in Employee Class (like I did in Department), to show OneToOne mapping between empId and deptHead,the code is compiling and running. However, If I do not add the mappedBy statement in Employee class, as the above code shows, the code still compiles and runs fine.
I would want to know why the code above works even if I am not providing mappedBy in employee class.
If anybody can help me clearing the above doubts and explaining the logic behind its working would be great as I am new to this.
It is not quite clear where you tried to user it with and without the mappedBy attribute.
But if I get your question correctly, you ask why you can have only one or both sides annotated?
It depends on which side is the source and destination of your relation or wheter it's bi-directional. On the Java-side you can have a relation always in both directions due to object references, but on the Database-side, you might only have it in one direction.
Check out JPA Wiki book on that topic for more details.
Additionally, the API doc for OneToOne states:
Specifies a single-valued association to another entity that has
one-to-one multiplicity. It is not normally necessary to specify the
associated target entity explicitly since it can usually be inferred
from the type of the object being referenced. If the relationship is
bidirectional, the non-owning side must use the mappedBy element of
the OneToOne annotation to specify the relationship field or property
of the owning side.

How to map existing JPA entities to PicketLink

I am trying to migrate a Seam 2 app to CDI and use PicketLink for security. After all the reading and researching, it seems like all the examples are having one to one mapping between PicketLink model and the backend entity. e.g. Account to AccountEntity, Partition to PartitionEntity. Since I already have entities in place representing identity model, I am stuck on trying to map them to PicketLink. Here is what I have:
#MappedSuperClass
public class ModelEntityBase implement Serializable {
#Id #Generated
Long id;
Date creationDate;
}
#Entity
public Account extends ModelEntityBase {
String username;
String passwordHash;
#OneToOne(mappedBy = "account")
Person person;
}
#Entity
public Person extends ModelEntityBase {
String name;
String email;
#OneToOne
#JoinColumn(name = "account_id")
Account account;
}
Two entities (plus a super class) representing a single identity model in PicketLink, e.g. stereo type User.
Based on this why IdentityType id is String not Long, I tried to add a new Entity in:
#Entity
#IdentityManaged(BaseIdentityType.class);
public class IdentityTypeEntity implement Serializble {
#Id #Identifier
private String id;
#OneToOne(optional = false, mappedBy = "identityType")
#OwnerReference
private Account account;
#IdentityClass
private String typeName;
#ManyToOne #OwnerReference
private PartitionEntity partition;
}
I've tried a few different ways with the annotation and model classes. But when using IdentityManager.add(myUserModel), I just can't get it to populate all the entities. Is this even possible?
Got help from Pedro (PicketLink Dev). Post the answer here to help others.
This is the model class I ended up using.
#IdentityStereotype(USER)
public class User extends AbstractAttributedType implements Account {
#AttributeProperty
private Account accountEntity;
#AttributeProperty
#StereotypeProperty(IDENTITY_USER_NAME)
#Unique
private String username;
#AttributeProperty
private boolean enabled;
#AttributeProperty
private Date createdDate;
#AttributeProperty
private Date expiryDate;
#AttributeProperty
private Partition partition;
// getter and setter omitted
}
And created a new entity to map to this model:
public class IdentityTypeEntity implements Serializable {
#Id
#Identifier
private String id;
#OneToOne(optional = false, mappedBy = "identityType",
cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#AttributeValue
// #NotNull
private HAccount accountEntity;
#IdentityClass
private String typeName;
#ManyToOne
#OwnerReference
private PartitionEntity partition;
#AttributeValue
private String username;
#AttributeValue
// #Transient
private boolean enabled;
#AttributeValue
private Date createdDate;
#AttributeValue
private Date expiryDate;
}
PL can map property with #AttributeProperty to entity property with #AttributeValue. But it can only map to one entity. Therefore there is no way to map, say User and its properties over to Account and Person. But you can have the entity (in my case accountEntity) in the model. I also have to duplicate a few fields in the new IdentityTypeEntity and my existing Account entity (username, eanbled, createdDate) because PL requires these. Use a #PrePersist and similar to sync them.

JPA Query Many To One nullable relationship

I have the following entities and would like to seek help on how to query for selected attributes from both side of the relationship. Here is my model. Assume all tables are properly created in the db. JPA provider I am using is Hibernate.
#Entity
public class Book{
#Id
private long id;
#Column(nullable = false)
private String ISBNCode;
#ManyToOne(cascade = CascadeType.DETACH, fetch = FetchType.LAZY, optional = false)
private Person<Author> author;
#ManyToOne(cascade = CascadeType.DETACH, fetch = FetchType.LAZY, optional = true)
private Person<Borrower> borrower;
}
#Inheritance
#DiscriminatorColumn(name = "personType")
public abstract class Person<T>{
#Id
private long id;
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private Info information;
}
#Entity
#DiscriminatorValue(PersonType.Author)
public class Author extends Person<Author> {
private long copiesSold;
}
#Entity
#DiscriminatorValue(PersonType.Borrower)
public class Borrower extends Person<Borrower> {
.....
}
#Entity
public class Info {
#Id
private long id;
#Column(nullable=false)
private String firstName;
#Column(nullable=false)
private String lastName;
......;
}
As you can see, the book table has a many to one relation to Person that is not nullable and Person that is nullable.
I have a requirement to show, the following in a tabular format -
ISBNCode - First Name - Last Name - Person Type
How can I write a JPA query that will allow me to select only attributes that I would want. I would want to get the attributes ISBN Code from Book, and then first and last names from the Info object that is related to Person Object that in turn is related to the Book object. I would not want to get all information from Info object, interested only selected information e.g first and last name in this case.
Please note that the relation between the Borrower and Book is marked with optional=true, meaning there may be a book that may not have been yet borrowed by someone (obviously it has an author).
Example to search for books by the author "Marc":
Criteria JPA Standard
CriteriaQuery<Book> criteria = builder.createQuery( Book.class );
Root<Book> personRoot = criteria.from( Book.class );
Predicate predicate = builder.conjunction();
List<Expression<Boolean>> expressions = predicate.getExpressions();
Path<Object> firtsName = personRoot.get("author").get("information").get("firstName");
expressions.add(builder.equal(firtsName, "Marc"));
criteria.where( predicate );
criteria.select(personRoot);
List<Book> books = em.createQuery( criteria ).getResultList();
Criteria JPA Hibernate
List<Book> books = (List<Book>)sess.createCriteria(Book.class).add( Restrictions.eq("author.information.firstName", "Marc") ).list();
We recommend using hibernate criterias for convenience and possibilities.
Regards,