Below is the simplified version of my data model:
#Entity
#Table(name = "DATA_TABLE")
public class DataTable implements Serializable {
#Id
#Column(name = "DATA_TABLE_ID")
private int dataTableId;
#Column(name = "TITLE")
private String title;
#OneToMany(mappedBy = "dataTableId", cascade = CascadeType.ALL, orphanRemoval = true)
private Set<Row> rows;
#Version
#Column(name = "VERSION")
private long version;
}
#Entity
#Table(name = "ROW")
public class Row implements Serializable {
#Id
#Column(name = "ROW_ID")
private int rowId;
#ManyToOne
#JoinColumn(name = "DATA_TABLE_ID")
private DataTable dataTable;
#OneToMany(mappedBy = "dataTableId", cascade = CascadeType.ALL, orphanRemoval = true)
private Set<Row> rows;
}
#Entity
#Table(name = "CELL")
public class Cell implements Serializable {
#Id
#Column(name = "CELL_ID")
private int cellId;
#ManyToOne
#JoinColumn(name = "ROW_ID")
private Row row;
#Column(name = "TEXT")
private String text;
}
I need to implement the optimistic locking for that model that is compatible with existing system.
In it, the version from the DataTable is sent to the user of the service, they have to send it back alongside the acion they performed and this is used to verify that there were no changes in the meantime. The version field is updated whenever there is a change to Row or Cell that belongs to that particular DataTable.
Unfortunately this does not seem to work with JPA #Version annotation(Hibernate 4.1.9). The version field is updated when there is a change to the properties persisted in DATA_TABLE table, e.g. title, but changing/adding rows and cells does not increment the version value.
Is there any way to achieve this functionality in JPA either by #Version or some other mechanism(expicit locking?) that prevents concurrency bugs such as Time of check to time of use?
I think it's because the field you're mapping to is a primitive type (long). You should use the Wrapper type Long instead.
JPA maintains a version number for every entity object. The initial version of a new entity object (when it is stored in the database for the first time) is 1. In every transaction in which an entity object is modified its version number is automatically increased by one. Version numbers are managed internally but can be exposed by defining a version field.
During commit (and flush), JPA checks every database object that has to be updated or deleted, and compares the version number of that object in the database to the version number of the in-memory object being updated. The transaction fails and an OptimisticLockException is thrown if the version numbers do not match, indicating that the object has been modified by another user (using another EntityManager) since it was retrieved by the current updater.
This behaviour happen only if the row is changed .. the foreign keys don't matter therefore the version number doesn't increase.
Related
I have this entity, in which I have made a PUT and POST method, which do not give an error but nevertheless the idMoneda, which is a property calculated with #Transient because it is the ID of the moneda(where there is a 1 to 1 relationship with another table), it does not update me, when I look at the database it remains null even though in the POST request I put a value. I don't know if it's because the setter is wrong, or just that something else needs to be added that I don't see right now.
#Entity
#Table(name = "REMESA")
public class Remesa {
#Id
#SequenceGenerator(name = "remesa_sequence", sequenceName = "remesa_sequence", allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "remesa_sequence")
#Column(name = "ID")
private Long id;
#OneToOne
#JoinColumn(name = "moneda", nullable = true)
#JsonIgnore
private Moneda moneda;
#Transient
#JsonProperty("moneda")
private Long idMoneda;
public Long getIdMoneda() {
return this.moneda.getId();
}
public void setIdMoneda(Long idMoneda) {
this.idMoneda = idMoneda;
}
}
#Transient in JPA means: do not save this field in DB. A column named "moneda_id" will automatically be generated by your relationship if it's well-defined
Java's transient keyword is used to denote that a field is not to be serialized, whereas JPA's #Transient annotation is used to indicate that a field is not to be persisted in the database, i.e. their semantics are different. Because they have different meanings.
So try to remove the transient annotation and run your code .
So I have two entity classes, Subscription and MailDetail. There is a one-to-one relation between them.
Here are the classes -
Subscription.class-
#Data
#Entity
#AllArgsConstructor
#Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED)
public class Subscription {
#Id
#GeneratedValue(strategy =`enter code here` GenerationType.SEQUENCE, generator = "subscription_subscription_id_seq")
#SequenceGenerator(initialValue = 1, allocationSize enter code here= 1, name = "subscription_subscription_id_seq", sequenceName = "subscription_subscription_id_seq")
#Column
private Long subscriptionId;
#Column
private String template;
#Column
private String fileFormat;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "mail_id", referencedColumnName = "mail_id")
private MailDetail mailDetail;
}
MailDetail.class -
#Data
#Entity
#Audited
public class MailDetail {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "mail_detail_mail_id_seq")
#SequenceGenerator(initialValue = 1, allocationSize = 1, name = "mail_detail_mail_id_seq", sequenceName = "mail_detail_mail_id_seq")
#Column
private Long mailId;
#Column
private String emailId;
}
So this creates two audit tables, subscription_aud and mail_detail_aud.
Creation/Updates in mail_detail table is done through Subscription repository only. So my requirement is that whenever there is a change in mail_details table's fields, I need an audit entry in subscription_aud. Or basically i want change in mail_detail table be considered as a change in subscription table.
I am using hibernate envers for auditing. How can i achieve this?
Unfortunately, not with how Envers is currently designed.
Any to-many association that is either had its contents modified or elements in the collection changed will propagate an audit change for the owning side of the relationship by default; however that can even be disabled by setting org.hibernate.envers.revision_on_collection_change to false. When this configuration setting is disabled, only element-collections will propagate a change to the owner because elements in such a collection are not entities, therefore can only be queried through the owning entity.
For to-one associations, this is a completely different story.
The only way to accomplish what you want would be to introduce some column on your Subscription instance that owns the MailDetail and change the value of that column when this use case happens. Based on the fact you only modify MailDetail via the aggregate root Subscription, this should work however I agree that its less than ideal.
I've been bashing my head on my keyboard for two days trying to figure this out...
Some background: We have a data model set up with a Perl code base that runs straight native SQL statements to the database via ODBC. For certain reasons, we decided to rewrite the code in Java... I thought it would be a good idea to use Hibernate to define all of the mappings. We don't want to edit the data model.
For simplicity sake, I can express the problem with only part of our data model. We have the entities "Job","JobDatabase" and "JobTable".
Job has a PK of job_name. Database has a PK of job_name,name. Table has a PK of job_name,src_database_name,name. As you may expect, Job has a OneToMany relationship with JobDatabase, and Database has a OneToMany with JobTable.
For purposes of this test, I'm starting with empty tables and trying to create some sample data. I can insert a Job and a JobDatabase, but when I try to insert the JobTable, Hibernate throws an error. Or more accurately, that is where it complains. It doesn't start executing my code because it detects the mapping error. However, if I remove the association between JobDatabase and JobTable, it will insert all Job and JobDatabase records correctly with no errors.
Sample Classes (all fields have getters/setters... there are also many other fields):
#Entity
#Table(name="Job")
public class Job implements Serializable {
#Id
#Column(name="job_name",nullable = false)
private String jobName;
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "job_name", referencedColumnName = "job_name")
private Set<JobDatabase> databases;
}
#Entity
#Table(name="JobDatabase")
public class JobDatabase implements Serializable {
#Id
#Column(name="job_name",nullable = false)
private String jobName;
#Id
#Column(name="name",nullable = false)
private String name;
#OneToMany(cascade = CascadeType.ALL)
#JoinColumns({
#JoinColumn(name = "job_name", referencedColumnName = "job_name"),
#JoinColumn(name = "name", referencedColumnName = "src_database_name")
})
private Set<JobTable> tables;
}
#Entity
#Table(name="JobTable")
public class JobTable implements Serializable{
#Id
#Column(name="job_name",nullable = false)
private String jobName;
#Id
#Column(name="src_database_name",nullable = false)
private String srcDatabaseName;
#Id
#Column(name="name",nullable = false)
private String name;
}
The error:
Exception in thread "main" org.hibernate.MappingException: Unable to find column with logical name: src_database_name in JobDatabase
I keep getting this error. I do not understand why it is looking for the referenced column in the entity "owning" the mapping. src_database_name does indeed only exist in JobTable - it is referred to as "name" in JobDatabase. JobTable also has a "name" field, but it refers to the name of the Table.
You need to have src_database_name column in your JobDatabase table. Or you can change src_database_name to other column name.
For composite key reference column must be present in your source table.
I'm trying to write a hibernate adapter for an old database schema. This schema does not have a dedicated id column, but uses about three other columns to join data.
On some tables, I need to use coalesce. This is what I came up with so far:
About the definition:
A car can have elements, assigned by the car's user or by the car's group of users.
If FORIGN_ELEMENT holds a user's name, definition will be 'u'
If FORIGN_ELEMENT holds a group's name, definition will be 'g'
This also means, one table (CAR_TO_ELEMENT) is misused to map cars to elements and cargroups to elements. I defined a superclass CarElement and subclasses CarUserElement and CarGroupElement.
state is either "active" or an uninteresting string
I set definitition and state elsewhere, we do not need to worry about this.
Use DEP_NR on the join table. If it's zero, use USR_DEP_NR. I did this with COALESCE(NULLIF()) successfully in native SQL and want to achieve the same in Hibernate with Pojos.
Okay, here we go with the code:
#Entity
#Table(name="CAR")
public class Car extends TableEntry implements Serializable {
#Id
#Column(name="DEP_NR")
private int depnr;
#Id
#Column(name="USER_NAME")
#Type(type="TrimmedString")
private String username;
#ManyToOne(fetch = FetchType.EAGER, targetEntity=CarGroup.class)
#JoinColumns(value={
#JoinColumn(name="GROUP_NAME"),
#JoinColumn(name="DEP_NR"),
#JoinColumn(name="state"),
})
private CarGroup group;
#OneToMany(fetch=FetchType.EAGER, targetEntity=CarUserElement.class, mappedBy="car")
private Set<CarUserElement> elements;
}
#Entity
#Table(name="CAR_GROUP")
public class CarGroup extends TableEntry implements Serializable {
#Id
#Column(name="DEP_NR")
private int depnr;
#Id
#Column(name="GROUP_NAME")
#Type(type="TrimmedString")
private String group;
#ManyToOne(fetch = FetchType.EAGER, targetEntity=Car.class)
#JoinColumns(value={
#JoinColumn(name="GROUP_NAME"),
#JoinColumn(name="DEP_NR"),
#JoinColumn(name="state"),
})
private Set<Car> cars;
#OneToMany(fetch=FetchType.EAGER, targetEntity=CarGroupElement.class, mappedBy="car")
private Set<CarGroupElement> elements;
}
#MappedSuperclass
public class CarElement extends TableEntry {
#Id
#ManyToOne(fetch = FetchType.EAGER, targetEntity=Element.class)
#JoinColumns(value={
#JoinColumn(name="ELEMENT_NAME"),
#JoinColumn(name="state"),
})
private Element element;
}
#Entity
#Table(name="CAR_TO_ELEMENT")
public class CarUserElement extends CarElement {
#Id
#Column(name="DEFINITION")
private char definition;
#Id
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumnsOrFormulas(value = {
#JoinColumnOrFormula(formula=#JoinFormula(value="COALESCE(NULLIF(DEP_NR, 0), USR_DEP_NR)", referencedColumnName="DEP_NR")),
#JoinColumnOrFormula(column=#JoinColumn(name="FORIGN_ELEMENT", referencedColumnName="USER_NAME")),
#JoinColumnOrFormula(column=#JoinColumn(name="STATE", referencedColumnName="STATE"))
})
private Car car;
}
#Entity
#Table(name="CAR_TO_ELEMENT")
public class CarGroupElement extends CarElement {
#Id
#Column(name="DEFINITION")
private char definition;
#Id
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumnsOrFormulas(value = {
#JoinColumnOrFormula(formula=#JoinFormula(value="COALESCE(NULLIF(DEP_NR, 0), USR_DEP_NR)", referencedColumnName="DEP_NR")),
#JoinColumnOrFormula(column=#JoinColumn(name="FORIGN_ELEMENT", referencedColumnName="GROUP_NAME")),
#JoinColumnOrFormula(column=#JoinColumn(name="STATE", referencedColumnName="STATE"))
})
private Car car;
}
I tried all available versions of hibernate (from 3.5.1 [first version with #JoinColumnsOrFormulas] up to 4.x.x), but I always get this error:
Exception in thread "main" java.lang.ClassCastException: org.hibernate.mapping.Formula cannot be cast to org.hibernate.mapping.Column
at org.hibernate.cfg.annotations.TableBinder.bindFk(TableBinder.java:351)
at org.hibernate.cfg.annotations.CollectionBinder.bindCollectionSecondPass(CollectionBinder.java:1338)
at org.hibernate.cfg.annotations.CollectionBinder.bindOneToManySecondPass(CollectionBinder.java:791)
at org.hibernate.cfg.annotations.CollectionBinder.bindStarToManySecondPass(CollectionBinder.java:719)
at org.hibernate.cfg.annotations.CollectionBinder$1.secondPass(CollectionBinder.java:668)
at org.hibernate.cfg.CollectionSecondPass.doSecondPass(CollectionSecondPass.java:66)
at org.hibernate.cfg.Configuration.originalSecondPassCompile(Configuration.java:1597)
at org.hibernate.cfg.Configuration.secondPassCompile(Configuration.java:1355)
at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1737)
at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1788)
Other hibernate users seem to have the same problem: They can't get it working with any version, see this thread and other stackoverflow questions:
https://forum.hibernate.org/viewtopic.php?f=1&t=1010559
To be more complete, here's my TrimmedString Class:
https://forum.hibernate.org/viewtopic.php?p=2191674&sid=049b85950db50a8bd145f9dac49a5f6e#p2191674
Thanks in advance!
PS: It works with joining just these three colulmns with just one DEP-NR-Column (i.e. either DEP_NR OR USR_DEP_NR using just #JoinColumns). But I need this coalesce(nullif()).
I ran into a similar problem, and it seems that the issue is that you are using a #Formula inside an #Id. Hibernate wants Ids to be insertable, and Formulas are read-only.
In my case I was able to work around the problem by making the individual columns Id properties on their own, and making the joined object a separate property. I don't know if this would work in your case since you're using two different columns in your formula, but if so your code might look something like:
#Entity
#Table(name="CAR_TO_ELEMENT")
public class CarUserElement extends CarElement {
#Id
#Column(name="DEFINITION")
private char definition;
#Id
#Column(name="DEP_NR")
private Integer depNr;
#Id
#Column(name="USR_DEP_NR")
private Integer usrDepNr;
#Id
#Column(name="FORIGN_ELEMENT")
private String userName;
#Id
#Column(name="STATE")
private String state;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumnsOrFormulas(value = {
#JoinColumnOrFormula(formula=#JoinFormula(value="COALESCE(NULLIF(DEP_NR, 0), USR_DEP_NR)", referencedColumnName="DEP_NR")),
#JoinColumnOrFormula(column=#JoinColumn(name="FORIGN_ELEMENT", referencedColumnName="USER_NAME", insertable = false, updatable = false)),
#JoinColumnOrFormula(column=#JoinColumn(name="STATE", referencedColumnName="STATE", insertable = false, updatable = false))
})
private Car car;
}
Join formulas are very fragile in Hibernate for the time being; I always had a difficult time to get them work properly.
The workaround that helped me often was to create database views which exposed the proper columns (including foreign keys that don't exist in the original tables). Then I mapped the entities to the views using classing Hibernate/JPA mappings.
Sometimes there are redundant joins in the generated SQL when using such entities, but the database optimizes such queries in most cases so that the execution plan is optimal anyway.
Another approach could be using #Subselects, which are some kind of Hibernate views, but I expect them to be less performant than the classic database views.
I ran into the cast exception as well and I'm on Hibernate 5.x.
Until Hibernate dedicates time to fix the issue, I found that while this guy's approach may not be cleanest (he even eludes to that fact!), it works.
You just need to add the #Column mappings (and get/set methods) to your association table objects that are returning null and manually set the values when you populate the relation data. Simple but effective!
I'm quite new to Hibernate and have been trying to determine what it will do for you and what it requires you to do.
A big one is dealing with an object that has dependants that don't yet exist in the database. For example, I have a Project object that includes a Manufacturer field that accepts a Manufacturer object as its value. In the database I have a products table with a mfr_id column that's a reference to the manufacturers table (a fairly typical unidirectional one-to-many relationship).
If the manufacturer assigned to the product object relates to one that's already in the database then there's no problem. However, when I try to save or update an object that references a manufacturer that hasn't been persisted yet, the operation fails with an exception.
Exception in thread "Application" org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing
I can of course manually check the state of the product's manufacturer by seeing if it's ID field is null and saving it if it is, but this seems like a cumbersome solution. Does Hibernate support automatically persisting dependants if the dependant in question isn't yet persisted? If so, how do I enable that behaviour? I'm using the version of Hibernate bundled with Netbeans (3.5, I believe) and inline annotations for specifying the mapping behaviour. Below are my product and manufacturer classes, cut down to the parts that handle the dependency. (Product extends Sellable which maps to a sellable table, using JOINED as the inheritance strategy It's that table that contains the primary key that identifies the product)
#Entity
#Table (
name="products",
schema="sellable"
)
public abstract class Product extends Sellable {
private Manufacturer manufacturer;
#ManyToOne (fetch = FetchType.EAGER)
#JoinColumn (name = "mfr_id")
public Manufacturer getManufacturer () {
return this.manufacturer;
}
/**
*
* #param manufacturer
*/
public Product setManufacturer (Manufacturer manufacturer) {
this.manufacturer = manufacturer;
return this;
}
}
The dependant Manufacturer
#Entity
#Table (
name="manufacturers",
schema="sellable",
uniqueConstraints = #UniqueConstraint(columnNames="mfr_name")
)
public class Manufacturer implements Serializable {
private Integer mfrId = null;
private String mfrName = null;
#Id
#SequenceGenerator (name = "manufacturers_mfr_id_seq", sequenceName = "sellable.manufacturers_mfr_id_seq", allocationSize = 1)
#GeneratedValue (strategy = GenerationType.SEQUENCE, generator = "manufacturers_mfr_id_seq")
#Column (name="mfr_id", unique=true, nullable=false)
public Integer getMfrId () {
return mfrId;
}
private Manufacturer setMfrId (Integer mfrId) {
this.mfrId = mfrId;
return this;
}
#Column(name="mfr_name", unique=true, nullable=false, length=127)
public String getMfrName () {
return mfrName;
}
public Manufacturer setMfrName (String mfrName) {
this.mfrName = mfrName;
return this;
}
}
UPDATE: I tried the following from this question, but I still get the transient object exception.
#ManyToOne (fetch = FetchType.EAGER, cascade = {CascadeType.PERSIST, CascadeType.MERGE})
I also checked what version of Hibernate is bundled with Netbeans, it's 3.2.5
UPDATE 2: I found that the following appears to apparently work as I wanted.
#ManyToOne (fetch = FetchType.EAGER, cascade = CascadeType.ALL)
However, I suspect that this is not the cascade type I really want. If I delete a product, I don't think deleting its associated manufacturer is the correct action, which is what I believe will happen now.
I did try creating a cascade type that consisted of all the types that were available, but that didn't work either. I got the same exception when I tried to save a product that had an unsaved manufacturer associated with it.
#ManyToOne (fetch = FetchType.EAGER, cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH})
I've seen CascadeType.SAVE_UPDATE mentioned in several places, but that mode doesn't seem to be available in the version of Hibernate that comes with Netbeans.
You have to look at cascading operations; this type of operation permits you to manage lifecycle of inner object respect their parent.
#ManyToOne(cascade) if you use Session.persist() operation or org.hibernate.annotations.#Cascade if you use not JPA function Session.saveOrUpdate().
This is just an example, for full doc point here
For your code, if you want to automatically save Manufacturer when saving Project use:
#ManyToOne (fetch = FetchType.EAGER, cascade = {javax.persistence.CascadeType.PERSIST})
#JoinColumn (name = "mfr_id")
public Manufacturer getManufacturer () {
return this.manufacturer;
}
or
#Cascade(CascadeType.PERSIST)