How do I auto-generate IDs for an #ElementCollection when it is a java.util.Map? - mysql

I’m using MySQL 5.5.37, JPA 2.0, and Hibernate 4.1.0.Final (I’m willing to upgrade if it solves my problem). I have the following entity …
#Entity
#Table(name = "url")
public class Url implements Serializable
{
…
#ElementCollection(fetch=FetchType.EAGER)
#MapKeyColumn(name="property_name")
#Column(name="property_value")
#CollectionTable(name="url_property", joinColumns=#JoinColumn(name="url_id"))
private Map<String,String> properties;
The “url_property” table has an ID (primary key) column, and perhaps for this reason, when I create a new Url entity with multiple properties, I feet the exception
[ERROR]: org.hibernate.engine.jdbc.spi.SqlExceptionHelper - Duplicate entry '' for key 'PRIMARY'
upon saving. Does anyone know what I have to do to auto-generate IDs for my url_property table? I would prefer not to write a trigger, but rather do something JPA, or at least, Hibernate sanctioned.
Edit: Per the first suggestion in the answer, I tried
#ElementCollection(fetch=FetchType.EAGER)
#Column(name="property_value")
#CollectionTable(name="url_property", joinColumns=#JoinColumn(name="url_id"))
private Set<UrlProperty> properties;
but it resulted in the exception, "org.hibernate.MappingException: Foreign key (FK24E4A95BB0648B:url_property [properties_id])) must have same number of columns as the referenced primary key (url_property [url_id,properties_id])".
My UrlProperty entity is
#Entity
#Table(name = "url_property")
public class UrlProperty
{
#Id
#GeneratedValue(generator = "uuid-strategy")
private String id;
#ManyToOne(fetch=FetchType.EAGER)
#JoinColumn(name="URL_ID")
private SubdomainUrl url;
#Column(name="PROPERTY_NAME")
private String propertyName;
#Column(name="PROPERTY_VALUE")
private String propertyValue;

You have only told JPA about 3 fields in the table ("property_name","property_value" and "url_id"), so it has no way of knowing about the 4th field used as the pk. Since it is not an entity, it doesn't have an Identity that is maintained. Options are:
1) Map the "url_property" table to a Property entity, which would have an ID, value and reference to the Url. The Url would then have a 1:M reference to the Property class, and can still be keyed on the name. http://wiki.eclipse.org/EclipseLink/Examples/JPA/2.0/MapKeyColumns has an example
2) Change your table to remove the ID field, and instead use "property_name","property_value" and "url_id" as the primary key.
3) Set a trigger to populate the ID. Doesn't seem useful though since the application is never aware of the field anyway.

Related

Is a Spring-data-jpa entity already indexed by the #EmbeddedId field?

I have the following spring-data-jpa entity:
#Entity(name = "absenceDays")
#Table
public final class MyTable {
#EmbeddedId
private MyId myId;
#Column(nullable = false)
private Long anotherId;
}
further, this is the #Embeddable entity used above:
public final class MyId implements Serializable {
#Column(updatable = false, nullable = false)
private Long id;
#Column(updatable = false, nullable = false)
private LocalDate date;
}
I have couple of questions?
Are tables already indexed with their primary keys? It seems to be implementation specific, as discussed here When should I use primary key or index?
How should I index my table with the composite id using the JPA 2.1 #Index annotation, if I need to index my table?
My DB of choice will be AWS RDS with MySQL InnoDB dialect.
Since you tagged the Question [mysql], I will address it from that point of view.
In MySQL the PRIMARY KEY is always UNIQUE and a KEY (aka INDEX). In the case of ENGINE=InnoDB, it is also "clustered" with the data. This makes fetching a row, given the PK, very fast.
To ask questions related to MySQL, it is best to dig below the 3rd party interface (Spring, in your case) to get to the MySQL (or MariaDB or Aurora) info, such as CREATE TABLE and SELECT....
I could probably answer your Q2 with the above info.

Why #JoinTable(name="user_role") not allowing multiple user_id with role_id

Am using springboot with hibernate,
My Entity classes looks like below :
#Entity
#Table(name="tbl_user")
public class User {
#Id #GeneratedValue(strategy=GenerationType.AUTO)
#Column(name="user_Id")
private long userId;
#Column(name="userName")
private String userName;
#Column(name="passWord")
private String passWord;
#OneToMany(cascade = CascadeType.ALL)
#JoinTable(name="user_role")
private Collection<Role> roleList;
My second entity looks like below :
#Entity
#Table(name="tbl_role")
public class Role {
#Id #GeneratedValue(strategy=GenerationType.AUTO)
#Column(name="role_Id")
private long roleId;
#Column(name="roleName")
private String roleName;
When I insert first user with role as manager(pkid=1), admin(pkid=2) its success but while I tried to insert 2nd user with role as Manager*pkid=1, admin(pkid=2, serviceUser(pkid=3) it's not allowing me to insert second user with below exception
com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Duplicate entry '2' for key 'UK_ixctfj5iq0enl7iktlpo7wxct'
Can somebody help me why this constraint is getting creating while generating tables, how can i insert 2nd user into DB ?
If you use OnetoMany on role_list you are effectively saying that a single User will point to many Roles and that a Role will point to only one User. This will be enforced with a unique key constraint placed on the join table. If you have the SQL statements printed out you will see it when the schema is created. Something along the lines of:
alter table user_role add constraint UK_ixctfj5iq0enl7iktlpo7wxct unique (role_id)
In your requirement, you also have a single Role used by many Users. Your admin role primary key is 2 and you want to be able to assign it to more than one user. Your relationship is a ManyToMany for the role_list.
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name="user_role")
private Collection<Role> roleList;
When you change the annotation, you will still have a join table, but no constraint will be added.

JPA doesn't t allow entity made of columns from multiple tables?

I know this makes none sense as many tutorials state that you can use SecondaryTable annotation, however it doesn't work in hibernate. I have schema like this:
#Entity
#Table(name="server")
#SecondaryTable(name="cluster", pkJoinColumns = { #PrimaryKeyJoinColumn(name = "uuid", referencedColumnName = "cluster_uuid") })
public class Server {
#Id
#Column(name = "uuid")
private String uuid;
#Column(name = "cluster_uuid")
private String clusterUuid;
#Column(name = "ip", table="cluster")
private String ip;
..... }
#Entity
#Table(name = "cluster")
public class Cluster {
#Id
#Column(name = "uuid")
private String uuid;
#Column(name = "ip")
private String ip;
.....
}
Server.clusterUuid is a foreign key to Cluster.uuid. I am hoping to get Server entity that fetches ip column from Cluster by joining Server.clusterUuid to Cluster.uuid.
Then I was greeted by a hibernate exception:
Caused by: org.hibernate.AnnotationException: SecondaryTable
JoinColumn cannot reference a non primary key
at org.hibernate.cfg.annotations.TableBinder.bindFk(TableBinder.java:402)
at org.hibernate.cfg.annotations.EntityBinder.bindJoinToPersistentClass(EntityBinder.java:620)
at org.hibernate.cfg.annotations.EntityBinder.createPrimaryColumnsToSecondaryTable(EntityBinder.java:612)
I see lots of people encountered this problem. But the first bug for this in Hibernate's bugzilla was 2010, I am surprised it's been there for over two years as this is supposed to be a basic feature. There is some post saying JPA spec only allows primary key to do the mapping, however, I get below from JPA wikibook
JPA allows multiple tables to be assigned to a single class. The
#SecondaryTable and SecondaryTables annotations or
elements can be used. By default the #Id column(s) are assumed to be
in both tables, such that the secondary table's #Id column(s) are the
primary key of the table and a foreign key to the first table. If
the first table's #Id column(s) are not named the same the
#PrimaryKeyJoinColumn or can be used to
define the foreign key join condition.
it's obviously OK for non-primary key. Then I am confused why Hibernate didn't fix this problem as it seems to be easy to implement by a join clause.
anybody knows how to overcome this problem? thank you.
I don't quite understand your setup.
#SecondaryTable is for storing a single entity in multiple tables, but in your case you have a many-to-one relationship between different entities (each one stored in its own table), and it should be mapped as such:
#Entity
#Table(name="server")
public class Server {
#ManyToOne
#JoinColumn(name = "cluster_uuid")
private Cluster cluster;
...
}

ejb3 toplink jpa 1.0 querying and sequencing

I have 2 questions:
suppose we have one entity named class and another called student. each class has onetomany students.
public class Clas implements Serializable {
#Id
#GeneratedValue(strategy=GenerationType.SEQUENCE)
private int id;
#OneToMany(cascade=CascadeType.ALL)
Collection<Student> students;
public clas(){
super();
}
..... getters and setters
}
q1: i get the exception there are no fields to be mapped, when adding any other column like String name, it works, but i don't need that field what can i do ?
q2: the ids is autogenerated, and i want to query all students in class c1, but i don't has the id of this class, how to do such query ?
iam working with mysql server glassfish v2.1 toplink jpa 1.0
Thanks
The student class must have a property named 'classID' (or whatever) that refers to the
Clas's id property. That should be annotated like #ManyToOne.
If that's done already by IDE, then check id generation strategy. For example, if you are using mysql, the primary key is auto_increment, then set th id's strategy to
GenerationType.AUTO and recompile. Tell me if any other errors shows up. :) .
ok. I think I understood you question. You may use NamedQueries written in Query Languages dependent on your library (in your case toplink) like EJB QL or HBQL. You can create Session Beans for querying.
public class ClassSessionBean {
#PersistenceContext(unitName="your PU name in persistence . xml")
private Entitymanager em;
publicClas selectByID(int id) throws NoResultException {
Query q = em.createQuery("select class from Class class where class.id=?");
q.setParameter(1, id);
Clas clas = q.getResultList();
return clas;
}
}
Note that the above code may contain syntax errors because I have not checked it anywhere.
Hope you find some help from this :) .

how to use em.merge() to insert OR update for jpa entities if primary key is generated by database?

I have an JPA entity like this:
#Entity
#Table(name = "category")
public class Category implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "id")
private Integer id;
#Basic(optional = false)
#Column(name = "name")
private String name;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "category")
private Collection<ItemCategory> itemCategoryCollection;
//...
}
Use Mysql as the underlying database. "name" is designed as a unique key. Use Hibernate as JPA provider.
The problem with using merge method is that because pk is generated by db, so if the record already exist (the name is already there) then Hibernate will trying inserting it to db and I will get an unique key constrain violation exception and not doing the update . Does any one have a good practice to handle that? Thank you!
P.S: my workaround is like this:
public void save(Category entity) {
Category existingEntity = this.find(entity.getName());
if (existingEntity == null) {
em.persist(entity);
//code to commit ...
} else {
entity.setId(existingEntity.getId());
em.merge(entity);
//code to commit ...
}
}
public Category find(String categoryName) {
try {
return (Category) getEm().createNamedQuery("Category.findByName").
setParameter("name", categoryName).getSingleResult();
} catch (NoResultException e) {
return null;
}
}
How to use em.merge() to insert OR update for jpa entities if primary key is generated by database?
Whether you're using generated identifiers or not is IMO irrelevant. The problem here is that you want to implement an "upsert" on some unique key other than the PK and JPA doesn't really provide support for that (merge relies on database identity).
So you have AFAIK 2 options.
Either perform an INSERT first and implement some retry mechanism in case of failure because of a unique constraint violation and then find and update the existing record (using a new entity manager).
Or, perform a SELECT first and then insert or update depending on the outcome of the SELECT (this is what you did). This works but is not 100% guaranteed as you can have a race condition between two concurrent threads (they might not find a record for a given categoryName and try to insert in parallel; the slowest thread will fail). If this is unlikely, it might be an acceptable solution.
Update: There might be a 3rd bonus option if you don't mind using a MySQL proprietary feature, see 12.2.5.3. INSERT ... ON DUPLICATE KEY UPDATE Syntax. Never tested with JPA though.
I haven't seen this mentioned before so I just would like to add a possible solution that avoids making multiple queries. Versioning.
Normally used as a simple way to check whether a record being updated has gone stale in optimistic locking scenario's, columns annotated with #Version can also be used to check whether a record is persistent (present in the db) or not.
This all may sound complicated, but it really isn't. What it boils down to is an extra column on the record whose value changes on every update. We define an extra column version in our database like this:
CREATE TABLE example
(
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
version INT, -- <== It really is that simple!
value VARCHAR(255)
);
And mark the corresponding field in our Java class with #Version like this:
#Entity
public class Example {
#Id
#GeneratedValue
private Integer id;
#Version // <-- that's the trick!
private Integer version;
#Column(length=255)
private String value;
}
The #Version annotation will make JPA use this column with optimistic locking by including it as a condition in any update statements, like this:
UPDATE example
SET value = 'Hello, World!'
WHERE id = 23
AND version = 2 -- <-- if version has changed, update won't happen
(JPA does this automatically, no need to write it yourself)
Then afterwards it checks whether one record was updated (as expected) or not (in which case the object was stale).
We must make sure nobody can set the version field or it would mess up optimistic locking, but we can make a getter on version if we want. We can also use the version field in a method isPersistent that will check whether the record is in the DB already or not without ever making a query:
#Entity
public class Example {
// ...
/** Indicates whether this entity is present in the database. */
public boolean isPersistent() {
return version != null;
}
}
Finally, we can use this method in our insertOrUpdate method:
public insertOrUpdate(Example example) {
if (example.isPersistent()) {
// record is already present in the db
// update it here
}
else {
// record is not present in the db
// insert it here
}
}