Composite Primary key in JPA - jpa

The EmbeddedId or IdClass annotation is used to denote a composite primary key.
How can i use composite primary key without ( EmbeddedId or IdClass ) ?
If it is possible to use composite primary key without ( EmbeddedId or IdClass ) then how can i use EntityManager.find( Entity Class , Object primaryKey) method to find entity in case of composite primary key(Multiple Primarykey) (because of no IdClass or EmbeddedId) .
EclipseLink take List of pk in the find() operation but if composite pk key defined in example -
Entity Person {
#Id
String username;
#Id
String emailId;
#Basic
String firstName;
#Basic
String lastName;
}
List list = new ArrayList();
list.add(${username}); //Run time value
list.add(${emailId}); //Run time value
then EnitityManager.find(list) will take these arguments , is i am right?
If i am assuming correct then how will EnitityManager.find() operation will know that List 1st argument is username or emailId pk value (means sequence of composite pk fields value)

Let me give my thoughts about it.
find
<T> T find(java.lang.Class<T> entityClass,
java.lang.Object primaryKey)
In order to find an entity of class Person, you should use something like
find(Person.class, Object primaryKey)
As you have a composed key, you should have a IdClass or EmbeddedId like this.
public class PersonKey implements Serializable{
String username;
String emailId;
public PersonKey(String username, String emailId){
//Add lines for correct constructor
}
//Override hascode and equals
}
Then you can find objects based on that key.
find(Person.class, personKey);
your key need to be something like this.
PersonKey personKey = new PersonKey(1,1);
Person p = find(Person.class, personKey);
FIND DON'T ACCEPT LISTS IN PURE JPA !!, accept and return only ONE managed object. If you would use find to retrieve several objects you should call the method N times passed the keys you want to find.
If you use find passing a list you will see something like this.
org.springframework.dao.InvalidDataAccessApiUsageException:
Provided id of the wrong type for class domain.model.Person. Expected:
class domain.key.PersonKey, got class java.util.ArrayList; nested
exception is java.lang.IllegalArgumentException: Provided id of the
wrong type for class
com.staples.sa.pricemart.domain.model.ItemFileEntity. Expected: class
com.staples.sa.pricemart.domain.key.ItemFileKey, got class
java.util.ArrayList
It seems eclipseLink have find that you can do that, but in order to make you application more portable try to use find as is described in JPA.

You should give an entity as the primary key:
Person id = new Person();
id.setUsername("Username");
id.setEmailId("EmailId");
entityManager.find(Person.class, id);
JPA will use the the fields annotated with #Id to find the the record You need.

You should be able to use a List with the find() operation in EclipseLink.

Related

JPA: receive several columns from table linked with current as manytoone

i'm new at JPA and need some advice.
I have two tables, Car and Driver linked as ManyTyOne. And I would obtain Car entity with info about several drivers. Now i can get from Driver only driver_id, it looks like this, but need to receive also drivers name and lastName.
Class Car:
#Entity
public class Car {
#JsonIgnoreProperties({"telephone", "mail",})
#ManyToOne
#JoinColumn(name = "driver_id"),
private Driver driver;
...
}
#Entity
class Driver:
#JsonIgnoreProperties({"telephone", "mail",})
public class Driver {
private Long id;
private String firstName;
private String lastName;
private String telephone;
private String mail;
...
}
Table car SQL:
CREATE TABLE `car` (
`id` decimal(20) unsigned NOT NULL AUTO_INCREMENT,
`driver_id` bigint(20) DEFAULT NULL,
PRIMARY KEY (`id`),
FOREIGN KEY (driver_id) REFERENCES driver(id),
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
At first I think that it's possible to receive also drivers name and surname the next way:
#JoinColumns({#JoinColumn(name = "driver_id"),
#JoinColumn(name = "first_name"),
#JoinColumn(name = "last_name")})
But documentation says, that #JoinColums is sutable only for composite fk.
Please give me a hint, how can i do whats needed.
Assuming your Driver class is an entity (you didn't put in the #Entity annotation in the above code.)
Your current mapping is saying that each driver can drive multiple cars. Since the many to one only exists on the Car side, there's no way to go from Driver to Car. To do that, you would need to have a #OneToMany mapping going from Driver to Car. (Also, shouldn't this be a many to many mapping? each Driver can drive multiple cars. and each car can have multiple drivers?)
In any case, given the mapping as above, you should be able to access driver.firstName and driver.lastName. What is the error when you are attempting to do that?

Storing ManyToMany with extra column using merge method

I have an entity class that is simply a ManyToMany with extra column, as follows:
#Entity
#Table(name = "view_templates_device_types")
public class ViewTemplateDeviceTypeEntity implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#ManyToOne
#JoinColumn(name = "view_template_id")
private ViewTemplateEntity viewTemplate;
#Id
#ManyToOne
#JoinColumn(name = "device_type_id")
private DeviceTypeEntity deviceType;
#Column(name = "priority", nullable = false)
private int priority;
public ViewTemplateDeviceTypeEntity() {
}
...
}
I noticed that when I create a new object of this type, set viewTemplate and deviceType to values that have corresponding data in the db and I call entityManager.persist(entity) the data is stored. But when I call entityManager.merge(entity) instead of persist I get an exception:
SQL Error: 1048, SQLState: 23000
Column 'view_template_id' cannot be null
I thought that calling merge should result with data inserted into database in case it is not stored yet. It is quite important to me to use merge here (because of cascades). What can I do to make it work?
As per the JPA spec, section 2.4
"A composite primary key must correspond to either a single persistent field or property or to a set of such fields or properties as described below. A primary key class must be defined to represent a composite primary key. Composite primary keys typically arise when mapping from legacy databases when the database key is comprised of several columns. The EmbeddedId or IdClass annotation is used to denote a composite primary key. See Sections 11.1.17 and 11.1.22.".
So you either need #IdClass or #EmbeddedId. Anything else is non-portable and prone to error. I am very surprised of any JPA provider that does not throw out warnings for this.

Using JPA CompositeKey uses a property from another Entity

I tried searching but was having problems finding what I want. I have the following schema( * indicates primary key)
USER
*UserId
-Desc
Registration
*DeviceId
*UserId
-date
So I want to create a primary key for Registration like...
#Embeddable
public class RegPk{
private String deviceId;
private User user;
#Column(name="DEV_ID")
public String getDevId(){
return deviceId;
}
...
#ManyToOne
#JoinColumn(name="USER_ID", referencedColumnName="USER_ID")
public User getUser() {
return user;
}
...
}
Is this right? Will it update the USER_ID field properly update in registration when I persist?
When I try this kind of thing out I get the following....
[10/7/13 13:37:07:156 EDT] 000000ae webapp E com.ibm.ws.webcontainer.webapp.WebApp logServletError SRVE0293E: [Servlet Error]-[Hello]: org.apache.openjpa.util.MetaDataException: The id class specified by type "class org.me.mfa.jpa.Registration" does not match the primary key fields of the class. Make sure your identity class has the same primary keys as your persistent type, including pk field types. Mismatched property: "user"
So what now?
JPA does not allow primary key classes to contain relationships, only basic types. JPA 2.0 allows relationships to be apart of the ID, but you would move the relationship to the entity class, and have RegPk contain a deviceId and UserId. The Device-User relationship would then be marked with either #Id or #MapsId("user") depending on if you wanted to use a #PKClass or #EmbeddedId within your entity. See http://wiki.eclipse.org/EclipseLink/Examples/JPA/2.0/DerivedIdentifiers for an example using a pk class.
In JPA 1.0, you would use a similar setup, except that your Device-User relationship would be marked read-only (either by specifying the #PrimaryKeyJoinColumn annotation, or by marking the #JoinColumn with insertable=false, updatable=false). You would then need to set the primary key basic mapping value manually, pulling the value from the referenced User entity directly. This of course requires that the id in User already be assigned, which might require additional work if both objects are new.
I would add cascade = CascadeType.ALL to #ManyToOne annotation to forward persist action for User entity.
It will look like:
...
#ManyToOne(optional=true, cascade = CascadeType.ALL)
#JoinColumn(name="USER_ID", referencedColumnName="USER_ID")
public User getUser() {
return user;
}
...
Well, if there is not other error, it could work. I'm little confused with optional=true, cuz it can provide id with deviceId,null but that could be OK also.

JPA Error : The entity has no primary key attribute defined

I am using JPA in my application. In one of the table, I have not used primary key (I know its a bad design).
Now the generated entity is as mentioned below :
#Entity
#Table(name="INTI_SCHEME_TOKEN")
public class IntiSchemeToken implements Serializable {
private static final long serialVersionUID = 1L;
#Column(name="CREATED_BY")
private String createdBy;
#Temporal( TemporalType.DATE)
#Column(name="CREATED_ON")
private Date createdOn;
#Column(name="SCH_ID")
private BigDecimal schId;
#Column(name="TOKEN_ID")
private BigDecimal tokenId;
public IntiSchemeToken() {
}
public String getCreatedBy() {
return this.createdBy;
}
public void setCreatedBy(String createdBy) {
this.createdBy = createdBy;
}
public Date getCreatedOn() {
return this.createdOn;
}
public void setCreatedOn(Date createdOn) {
this.createdOn = createdOn;
}
public BigDecimal getSchId() {
return this.schId;
}
public void setSchId(BigDecimal schId) {
this.schId = schId;
}
public BigDecimal getTokenId() {
return this.tokenId;
}
public void setTokenId(BigDecimal tokenId) {
this.tokenId = tokenId;
}
}
Here In my project, eclipse IDE shows ERROR mark(RED colored cross) on this class and the error is "The entity has no primary key attribute defined".
Can anyone tell me, How to create an entity without primary key ?
Thanks.
You can't. An entity MUST have a unique, immutable ID. It doesn't have to be defined as a primary key in the database, but the field or set of fields must uniquely identify the row, and its value may not change.
So, if one field in your entity, or one set of fields in your entity, satisfies these criteria, make it (or them) the ID of the entity. For example, if there is no way that a user can create two instances in the same day, you could make [createdOn, createdBy] the ID of the entity.
Of course this is a bad solution, and you should really change your schema and add an autogenerated, single-column ID in the entity.
If your Primary Key(PK) is a managed super class which is inherited in an entity class then you will have to include the mapped super class name in the persistence.xml file.
Look at the bug report:
https://bugs.eclipse.org/bugs/show_bug.cgi?id=361042
If you need to define a class without primary key, then you should mark that class as an Embeddable class. Otherwise you should give the primary key for all entities you are defining.
You can turn off (change) validation that was added.
Go to workspace preferences 'Java Persistence->JPA->Errors/Warnings' next 'Type' and change 'Entity has no primary key' to 'Warnning'.
In addition to http://en.wikibooks.org/wiki/Java_Persistence/Identity_and_Sequencing#No_Primary_Key you can use some build-in columns like ROWID in Oracle:
Oracle legacy table without good PK: How to Hibernate?
but with care:
http://www.orafaq.com/wiki/ROWID
Entity frameworks doesn't work for all kind of data (like statistical data which was used for analysis not for querying).
Another solution without Hibernate
If
- you don't have PK on the table
- there is a logical combination of columns that could be PK (not necessary if you can use some kind of rowid)
-- but some of the columns are NULLable so you really can't create PK because of DB limitation
- and you can't modify the table structure (would break insert/select statements with no explicitly listed columns at legacy code)
then you can try the following trick
- create view at database with virtual column that has value of concatenated logical key columns ('A='||a||'B='||'C='c..) or rowid
- create your JPA entity class by this view
- mark the virtual column with #Id annotation
That's it. Update/delete data operations are also possible (not insert) but I wouldn't use them if the virtual key column is not made of rowid (to avoid full scan searches by the DB table)
P.S. The same idea is partly described at the linked question.
You need to create primary key ,If not found any eligible field then create auto increment Id.
CREATE TABLE fin_home_loan (
ID int NOT NULL AUTO_INCREMENT,
PRIMARY KEY (ID));
Just add fake id field.
In Postgres:
#Id
#Column(name="ctid")
String id;
In Oracle:
#Id
#Column(name="ROWID")
String rowid;

How can I change the default JPA ID field in the Play Framework?

I'm currently creating some POJOs to connect to an existing database called User. The database already has a userid that I would like to use. However the framework creates an id field that it appends to the end of the table. How can I specify that the framework should use the pre-existing userid field and not create a new one?
You can use #AttributeOverride:
#Entity
#AttributeOverride(name = "id", column = #Column(name = "userid"))
public class User extends Model { ... }
To define your own primary key, have your models extend GenericModel instead of model and annotate your primary key with #Id. The model class enhances the generic model with an autogenerated id.
class User extends GenericModel{
#Id
Long userid;
}