My scenario is very simple. I have an entityID identity field in the #Entity class and the DB (Oracle, which I'm not sure that matters):
#Id
#SequenceGenerator(name="ENTITY_SEQ_GEN", sequenceName="SEQ_GENERIC", allocationSize = 1)
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="ENTITY_SEQ_GEN")
#Column(name="ENTITY_ID")
private long entityID;
I have another field called, let's say, entityReadableID and that should be a String consisting of the stringified entityID concatenated with another String field from the entity. E.g. if entityID is 1234, entityReadableID may be something like 1234ABC.
My problem is that, as far as I know, the value of entityID is not known before the row is created in the DB but I need to concatenate the entityReadableID using its value. Is there a way to fetch the value of the sequence generated ID before the row is created in the DB so that I can use it to generate the other ID? I know I can make it an insert with that field being null and then make an update once I know what entityID is but that solution seems less than elegant.
The way I am hoping Hibernate/Oracle may be able to support this is if Hibernate can somehow "reserve/issue" the next generated value for the entity being processed before the actual persistence, let me know what it is so I can manipulate with it, then at the end persist it.
You can get the generated Id before persisting of that entity by not using sequence directly for that entity, I mean use a separated entity for that sequence, so your entity should be something like:
#Entity
#Table(name = "ENTITY"
)
public class EntityClass implements java.io.Serializable {
private Long entityId;
private String entityRelatedId;
public EntityClass() {
}
// you may have other constructors
#Id
#Column(name = "ENTITY_ID", nullable = false)
public Long getEntityId() {
return this.entityId;
}
public void setEntityId(Long entityId) {
this.entityId = entityId;
}
#Column(name = "ENTITY_RELATED_ID", length = 50)
public String getEntityRelatedId() {
return this.entityRelatedId;
}
public void setEntityRelatedId(String entityRelatedId) {
this.entityRelatedId = entityRelatedId;
}
}
and the entity for the sequence is something like:
#Entity
#Table(name = "SEQ_GENERIC_TBL"
)
public class SeqGenericTbl implements java.io.Serializable {
private Long id;
public SeqGenericTbl() {
}
public SeqGenericTbl(Long id) {
this.id = id;
}
#Id
#SequenceGenerator(name = "ENTITY_SEQ_GEN",
sequenceName = "SEQ_GENERIC", allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "ENTITY_SEQ_GEN")
#Column(name = "ID")
public Long getId() {
return this.id;
}
public void setId(Long id) {
this.id = id;
}
}
Now you can get the Id from the entity of the sequence (persist it, you can rollback that tran or delete it or empty the related table later):
SessionFactory sf = HBUtil.getSessionFactory();
Session s = sf.openSession();
SeqGenericTbl sg=new SeqGenericTbl();
s.save(sg);
EntityClass entity1 = new EntityClass();
entity1.setEntityId(sg.getId());
//NOW YOU HAVE THE ID WITHOUT PERSISTING THE ENTITY
System.out.println(entity1.getEntityId());
Related
I have a class, for example
#Setter
#EqualsAndHashCode(of = "id")
#Entity
public class MovieBoxOfficeEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Setter(AccessLevel.NONE)
private Long id;
}
which has an ID generated automatically without being manually set. I also have a class with a method
#Entity
#Table(name = "movies")
public class MovieEntity {
List<MovieBoxOffice> boxOffices = new ArrayList<>();
public void addBoxOffice(MovieBoxOffice boxOffice) throws ResourceConflictException {
if (this.boxOffices.contains(boxOffice)) {
throw new ResourceConflictException("A box office with id " + boxOffice.getId() + " is already added");
}
this.boxOffices.add(boxOffice);
}
}
There is a problem with testing the method addBoxOffice, because the comparison of MovieBoxOfficeEntity objects is done using the ID, and the ID is only generated automatically when writing to the database and can not be set manually.
I came up with the idea of adding the uniqueId field to the MovieBoxOfficeEntity class and adding it to the #EqualsAndHashCode annotation
#Setter
#EqualsAndHashCode(of = {"id", "uniqueId"})
#Entity
public class MovieBoxOfficeEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Setter(AccessLevel.NONE)
private Long id;
private String uniqueId = UUID.randomUUID().toString();
}
then I can test this way
#Test(expected = ResourceConflictException.class)
public void canAddBoxOffice() throws ResourceConflictException{
final String id = UUID.randomUUID().toString();
final MovieBoxOfficeEntity boxOffice = new MovieBoxOfficeEntity();
boxOffice.setUniqueId(id);
this.movieEntity.addBoxOffice(boxOffice);
this.movieEntity.addBoxOffice(boxOffice);
}
in this way, the object boxOffice will have
id: null
uniqueId: some generated UUID
and the comparison of objects will take place on the uniqueId comparison.
What do you think about creating a uniqueId field just to test the entity methods?
Adding uniqueId can lead to collisions. Suppose there are two finders. When their result is created, two instances will be created for the same rows in the database. They will have the same id, but different uniqueId. Means, two objects have the same primary key, but equals return false. This can lead to errors in the application logic and in the JPA implementation.
You can override equals. But use the entity attributes that are related to your table, not the randomly generated UUIDs.
I have tables with structure:
orders
- id: bigint(20)
- amount: bigint(20)
order_details
- id: bigint(20)
- payment_type: varchar(255)
- order_fk: bigint(20)
Entities:
MyOrderEntity
#Entity
#Table(name = "orders")
public class MyOrderEntity {
#Id
#GeneratedValue(strategy = IDENTITY)
public Long id;
public Long amount;
#OneToOne(fetch = LAZY, mappedBy = "order", cascade = ALL)
public MyOrderDetailsEntity details;
}
MyOrderDetailsEntity
#Entity
#Table(name = "order_details")
public class MyOrderDetailsEntity {
#Id
#GeneratedValue(strategy = IDENTITY)
public Long id;
#OneToOne
#JoinColumn(name = "order_fk")
public MyOrderEntity order;
public String paymentType;
}
Repository:
#Repository
public interface MyOrderRepository extends JpaRepository<MyOrderEntity, Long> {}
I'm persisting MyOrderEntity in such way:
MyOrderDetailsEntity details = new MyOrderDetailsEntity();
details.paymentType = "default";
MyOrderEntity order = new MyOrderEntity();
order.amount = 123L;
order.details = details;
myOrderRepository.save(order);
After order saving I have null value in order_details.order_fk field.
I want that order_details.order_fk will be filled by order.id.
How can I do this?
You need also to explicitly set the MyOrderEntity to MyOrderDetailsEntity. JPA implementation does not do it for you. So add line:
details.order = order;
before save.
You can also add following method to MyOrderEntity:
#PrePersist
private void prePersist() {
if(null!=details) details.order=this;
}
to avoid boilerplate code everywhere you set the MyOrderDetailsEntity to MyOrderEntity.
But the best way is to set MyOrderDetailsEntity.details field private and create a setter like:
setDetails(MyOrderDetailsEntity details) {
this.details = details;
details.order = this;
}
to keep it always set correctly, even before persisting. Best strategy depends on the case.
See this question and answers for more details.
I have a (abbreviated) class that looks like this:
#Entity
#Table
#SecondaryTable(
name = "SUPER_ADMIN",
pkJoinColumns = #PrimaryKeyJoinColumn(
name = "PERSON_ID",
referencedColumnName = "PERSON_ID"))
public class Person {
#Id
#Column(name = "PERSON_ID")
private Long personId;
// getters/setters omitted for brevity
}
The SUPER_ADMIN table has only one column: PERSON_ID. What I would like to do is add private Boolean superAdmin to Person where it would be true if the PERSON_ID is present in that table.
Is this even possible? I am using Hibernate as my JPA provider, so I'm open to proprietary solutions as well.
UPDATE
It seems like I should have done more homework. After poking around, I see that #SecondaryTable does inner joins and not outer joins. Therefore, my idea here will not work at all. Thanks to #Elbek for the answer -- it led me to this revelation.
You can use JPA callback methods.
public class Person {
#Id
#Column(name = "PERSON_ID")
private Long personId;
#Transient
private transient Boolean superAdmin = false;
// This method will be called automatically when object is loaded
#PostLoad
void onPostLoad() {
// BTW, personId has to be present in the table since it is id column. Do you want to check if it is 1?
superAdmin = personId == 1;
}
}
or you can create easy getter method.
public class Person {
#Id
#Column(name = "PERSON_ID")
private Long personId;
boolean isSuperAdmin() {
return personId == 1;
}
}
You can't have an optional relationship with a #SecondaryTable. You do not have any other choice than using a #OneToOne optional relationship in that case.
I have problem with updating a database record using EJB and JPA. Persistence provider: org.eclipse.persistence.jpa.PersistenceProvider
When I am creating a record I am using this method:
public void create(T entity) {
getEntityManager().persist(entity);
}
All is ok. Now I want to edit the the same record. For e.g. I have an entity:
#Entity
#Table(name = "OPERATION")
public class Operation implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private Long id;
#Column(name = "OPERATION_AUTHOR")
private String operationAuthor;
#Column(name = "OPERATION_TYPE")
private String operationType;
#Column(name = "OPERATION_STATUS")
private String operationStatus;
#Column(name = "CREATED")
#Temporal(value = TemporalType.DATE)
private Date created;
#Column(name = "COMPLETED")
#Temporal(value = TemporalType.DATE)
private Date completed;
//Getters and setters
}
For e.g. I want to update only operationStatus. I am creating an entity, setting to it the same record id and new operationStatus. For updating I am using this method:
public void edit(T entity) {
getEntityManager().merge(entity);
}
The problem is when I update the record the status is updated correctly but all the other columns' values are set to null not left as they were before. I want to update only operationStatus and left other values untouched. Is this possible to do this using EJB? And what should I change to make this happen?
Load the entity first, using Operation op = getEntityManager().find(Operation.class,id). Then do the op.setOperationStatus(value). That's all, it will get updated on session flush/close.
The issue is that calling entityManager.merge(entity) will update the db row (corresponding to the ID you set on the object) with the same exact values that populate the java object (i.e., everything except status and id are null). You need to get the object out of the database and then update it.
A better option for this may be the following method:
/**
*
* #param id - the ID of the entity to update
* #param operationStatus - the status to apply
*/
public void setOperationStatus(long id, String operationStatus){
Operation o = getEntityManager().find(Operation.class,id);
o.setOperationStatus(operationStatus);
getEntityManager().merge(entity);
}
Can anyone tell me whether Hibernate supports associations as the pkey of an entity? I thought that this would be supported but I am having a lot of trouble getting any kind of mapping that represents this to work. In particular, with the straight mapping below:
#Entity
public class EntityBar
{
#Id
#OneToOne(optional = false, mappedBy = "bar")
EntityFoo foo
// other stuff
}
I get an org.hibernate.MappingException: "Could not determine type for: EntityFoo, at table: ENTITY_BAR, for columns: [org.hibernate.mapping.Column(foo)]"
Diving into the code it seems the ID is always considered a Value type; i.e. "anything that is persisted by value, instead of by reference. It is essentially a Hibernate Type, together with zero or more columns." I could make my EntityFoo a value type by declaring it serializable, but I wouldn't expect this would lead to the right outcome either.
I would have thought that Hibernate would consider the type of the column to be integer (or whatever the actual type of the parent's ID is), just like it would with a normal one-to-one link, but this doesn't appear to kick in when I also declare it an ID. Am I going beyond what is possible by trying to combine #OneToOne with #Id? And if so, how could one model this relationship sensibly?
If the goal is to have a shared primary key, what about this (inspired by the sample of Java Persistence With Hibernate and tested on a pet database):
#Entity
public class User {
#Id
#GeneratedValue
private Long id;
#OneToOne(cascade = CascadeType.ALL)
#PrimaryKeyJoinColumn
private Address shippingAddress;
//...
}
This is the "parent" class that get inserted first and gets a generated id. The Address looks like this:
#Entity
public class Address implements Serializable {
#Id #GeneratedValue(generator = "myForeignGenerator")
#org.hibernate.annotations.GenericGenerator(
name = "myForeignGenerator",
strategy = "foreign",
parameters = #Parameter(name = "property", value = "user")
)
#Column(name = "ADDRESS_ID")
private Long id;
#OneToOne(mappedBy="shippingAddress")
#PrimaryKeyJoinColumn
User user;
//...
}
With the above entities, the following seems to behave as expected:
User newUser = new User();
Address shippingAddress = new Address();
newUser.setShippingAddress(shippingAddress);
shippingAddress.setUser(newUser); // Bidirectional
session.save(newUser);
When an Address is saved, the primary key value that gets inserted is the same as the primary key value of the User instance referenced by the user property.
Loading a User or an Address also just works.
Let me know if I missed something.
PS: To strictly answer the question, according to Primary Keys through OneToOne Relationships:
JPA 1.0 does not allow #Id on a OneToOne or ManyToOne, but JPA 2.0 does.
But, the JPA 1.0 compliant version of Hibernate
allows the #Id annotation to be used on a OneToOne or ManyToOne mapping*.
I couldn't get this to work with Hibernate EM 3.4 though (it worked with Hibernate EM 3.5.1, i.e. the JPA 2.0 implementation). Maybe I did something wrong.
Anyway, using a shared primary key seems to provide a valid solution.
Yes that is possible.
Look at the following example using Driver and DriverId class as id for Driver.
#Entity
public class Drivers {
private DriversId id; //The ID which is located in another class
public Drivers() {
}
#EmbeddedId
#AttributeOverrides({
#AttributeOverride(name = "personId", column = #Column(name = "person_id", nullable = false))})
#NotNull
public DriversId getId() {
return this.id;
}
//rest of class
}
Here we are using personId as the id for Driver
And the DriversId class:
//composite-id class must implement Serializable
#Embeddable
public class DriversId implements java.io.Serializable {
private static final long serialVersionUID = 462977040679573718L;
private int personId;
public DriversId() {
}
public DriversId(int personId) {
this.personId = personId;
}
#Column(name = "person_id", nullable = false)
public int getPersonId() {
return this.personId;
}
public void setPersonId(int personId) {
this.personId = personId;
}
public boolean equals(Object other) {
if ((this == other))
return true;
if ((other == null))
return false;
if (!(other instanceof DriversId))
return false;
DriversId castOther = (DriversId) other;
return (this.getPersonId() == castOther.getPersonId());
}
public int hashCode() {
int result = 17;
result = 37 * result + this.getPersonId();
return result;
}
}
You can do this by sharing a primary key between EntityFoo and EntityBar:
#Entity
public class EntityBar
{
#Id #OneToOne
#JoinColumn(name = "foo_id")
EntityFoo foo;
// other stuff
}
#Entity
public class EntityFoo
{
#Id #GeneratedValue
Integer id;
// other stuff
}
You have to use #EmbeddedId instead of #Id here.
And EntityFoo should be Embeddable.
Another way is to put an integer, and a OneToOne with updateble and instertable set to false.