I have an entity defined as follows
#Entity(name = "Report")
#Table(name = "REPORTS")
#org.hibernate.annotations.Entity(dynamicInsert = true, dynamicUpdate = true, selectBeforeUpdate = true)
public class Report implements java.io.Serializable {
/* other fields, getters and setters*/
#Column(name = "UPD_TIMESTAMP")
#Version
private Long updTimestamp;
#OneToMany(mappedBy = "report", fetch = FetchType.LAZY)
private Collection<ReportItem> reportItems = new ArrayList<ReportItem>();
public Collection<ReportItem> getReportItems() {
return reportItems;
}
public void setReportItems(Collection<ReportItems> reportItems) {
this.reportItems = reportItems;
}
}
The problem is that when I modify something in reportItems, the Report entity becomes dirty and there is always an update that increments the version field only.
I know abut #OptimisticLock(excluded=true), but i'm stuck to Hibernate 3.2.0 GA and this annotation isn't available. Is there any workaround to this feature that I can use with Hibernate 3.20 GA?
Take a look at the inverse keyword, maybe this can provide a solution. Here it is explained.
Edit: Turns out this is expected behavior. See this question
Related
This is a follow-up question to my previous one How to model packages, versions and licenses?.
Here is my database setup.
V1__create_table_license.sql
CREATE TABLE IF NOT EXISTS license (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
reference TEXT NOT NULL,
is_deprecated_license_id BOOLEAN NOT NULL,
reference_number INTEGER NOT NULL,
license_id TEXT NOT NULL,
is_osi_approved BOOLEAN NOT NULL
);
INSERT INTO license
("name",reference,is_deprecated_license_id,reference_number,license_id,is_osi_approved)
VALUES
('MIT License','./MIT.json',false,275,'MIT',true);
V2__create_npm_package.sql
CREATE TABLE IF NOT EXISTS npm_package (
id BIGSERIAL PRIMARY KEY,
name TEXT NOT NULL,
description TEXT NOT NULL
);
INSERT INTO npm_package
(name, description)
VALUES
('react', 'React is a JavaScript library for building user interfaces.'),
('react-router-dom', 'DOM bindings for React Router'),
('typescript', 'TypeScript is a language for application scale JavaScript development'),
('react-dom', 'React package for working with the DOM.');
V3__create_npm_version.sql
CREATE TABLE IF NOT EXISTS npm_package_version (
npm_package_id BIGINT NOT NULL REFERENCES npm_package,
version TEXT NOT NULL,
license_id INTEGER NOT NULL REFERENCES license,
UNIQUE(npm_package_id, version)
)
Here are my Java objects.
License.java
#Entity
public class License {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String reference;
private Boolean isDeprecatedLicenseId;
private Integer referenceNumber;
private String name;
private String licenseId;
private Boolean isOsiApproved;
}
LicenseRepository.java
public interface LicenseRepository extends JpaRepository<License, Integer> {
License findByLicenseIdIgnoreCase(String licenseId);
}
NpmPackage.java
#Entity
public class NpmPackage {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String description;
#OneToMany(mappedBy = "npmPackage", cascade = CascadeType.ALL, orphanRemoval = true)
private List<NpmPackageVersion> versions = new ArrayList<>();
public NpmPackage() {}
public void addVersion(NpmPackageVersion version) {
this.versions.add(version);
version.setNpmPackage(this);
}
public void removeVersion(NpmPackageVersion version) {
this.versions.remove(version);
version.setNpmPackage(null);
}
}
#Entity
public class NpmPackageVersion {
public NpmPackageVersion() {}
public NpmPackageVersion(String version, License license) {
this.setVersion(version);
this.license = license;
}
#EmbeddedId private NpmPackageIdVersion npmPackageIdVersion = new NpmPackageIdVersion();
#MapsId("npmPackageId")
#ManyToOne(fetch = FetchType.LAZY)
private NpmPackage npmPackage;
#ManyToOne(fetch = FetchType.LAZY)
private License license;
#Embeddable
public static class NpmPackageIdVersion implements Serializable {
private static final long serialVersionUID = 3357194191099820556L;
private Long npmPackageId;
private String version;
// ...
}
public String getVersion() {
return this.npmPackageIdVersion.version;
}
public void setVersion(String version) {
this.npmPackageIdVersion.version = version;
}
}
MyRunner.java
#Component
class MyRunner implements CommandLineRunner {
#Autowired LicenseRepository licenseRepository;
#Autowired NpmPackageRepository npmPackageRepository;
#Override
// #Transactional
public void run(String... args) throws Exception {
// get license from database
var license = licenseRepository.findByLicenseIdIgnoreCase("mit");
// get package from db
var dbPackage = npmPackageRepository.findByNameIgnoreCase("react");
var version = new NpmPackageVersion("1.0.0", license);
dbPackage.addVersion(version);
npmPackageRepository.save(dbPackage);
}
}
In my previous question I got the answer to use fetch = FetchType.EAGER but then I learned that this is not ideal. I'd like to use lazy fetching.
#OneToMany(mappedBy = "npmPackage", fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
private List<NpmPackageVersion> versions = new ArrayList<>();
So I removed the eager fetching and run into an error.
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.example.bom.NpmPackage.NpmPackage.versions, could not initialize proxy - no Session
With the #Transactional annotation everything works. Why is this the case? I tried to read everything online but I still don't really get it. I understand that the database session is closed at some point and I wonder where exactly this is the case. I also wonder if I could do something about, e.g. I tried to fetch all versions to ensure they are loaded before I add another one.
So do I really have to use #Transactional or is there another solution? I just want to understand the "magic" going on :)
Thank you very much!
When you use FetchType.LAZY, Hibernate ORM doesn't really return an initialized collection when you find the entity. The association is going to be a proxy and when you need access to the collection, Hibernate ORM is going to query the database and get it.
To achieve this, the entity (the NpmPackage) needs to be in a managed state. If the entity is not managed and you try to access a lazy association (versions in this case), you get the LazyInitializationException.
In your example, when you use #Transactional, the entity stays managed for the duration of the method. Without it, it becomes not managed as soon as you return from findByNameIgnoreCase.
If you know that you will need the association versions, you could also use a fetch join query to get the NpmPackage:
from NpmPackage p left join fetch p.versions where p.name=:name
This way the associations stays lazy but you can get it with a single query.
I am making a Spring Boot backend, and I have the following problem. When I get a Software from VersionableFileRepository and call the getSystem function on that I get the actual System within the relationship. But when I get a Documentation from VersionableFileRepository its getSystem function returns null. I handle the Software and Documentation in the same way, and all instance of these have a System.
Illustrated with code:
versionableFileRepository.findById(fileId).get().getSystem() returns a valid System when fileId identify a Software and returns null when a Documentation
What's wrong? Did I mess something up in the implementation?
I have the following classes:
#Entity
public class System {
#Id
#GeneratedValue
private long id;
private String name;
#OneToOne(cascade = CascadeType.REMOVE, orphanRemoval = true)
#JoinColumn(name = "software_id", referencedColumnName = "id")
private Software software;
#OneToOne(cascade = CascadeType.REMOVE, orphanRemoval = true)
#JoinColumn(name = "documentation_id", referencedColumnName = "id")
private Documentation documentation;
//other fields, getters and setters...
}
#Entity
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class VersionableFile {
#Id
#GeneratedValue
private long id;
#OneToMany(mappedBy = "file", cascade = CascadeType.REMOVE, orphanRemoval = true)
private List<FileVersion> versions = new ArrayList<>();
public abstract System getSystem();
public abstract void setSystem(System system);
//getters and setters...
}
#Entity
public class Software extends VersionableFile {
#OneToOne(mappedBy = "software")
#JsonIgnore
private System system;
#Override
public System getSystem() {
return system;
}
#Override
public void setSystem(System system) {
this.system = system;
}
}
#Entity
public class Documentation extends VersionableFile {
#OneToOne(mappedBy = "documentation")
#JsonIgnore
private System system;
#Override
public System getSystem() {
return system;
}
#Override
public void setSystem(System system) {
this.system = system;
}
}
#Repository
public interface VersionableFileRepository extends CrudRepository<VersionableFile, Long> {
}
Database:
Everything looks good in the database, this is the system table:
And the corresponding objects can be found in the other two tables (software and documentation). Furthermore the appropriate constraints are also defined.
I think this is a JPA issue, because when I get a System object from SystemRepository (not mentioned here) it has the right software and documentation fields.
Thank you in advance for your help!
Have already commented but looking better I think I found something major here.
Proposal 1
Your Entities structure seems good to me. However you have a major Issue with your java code to retrieve those entities back from database.
versionableFileRepository.findById(fileId).get().getSystem()
fileId as well as documentId are plain Long numbers. How would JPA know if you want to retrieve a Software or a Documentation? This will not work. As you have constructed it, it will have separate tables Documentation and Software and each one of those will have a column Id as primary key.
Make it easier for JPA by using specific repositories
#Repository
public interface SoftwareRepository extends CrudRepository<Software, Long> {
}
Then to retrieve software just use softwareRepository.findById(id).get().getSystem()
And
#Repository
public interface DocumentationRepository extends CrudRepository<Documentation, Long> {
}
Then to retrieve documentation just use documentationRepository.findById(id).get().getSystem()
Proposal 2
If you wish to go along the way you are going then I would consider that the error is specifically on your ids that are generated. You want different tables in your case Documentation and Software to have distinct Ids. Then JPA could distinct from the Id what entity you have.
To achieve that you have to change the strategy of generating Ids
public abstract class VersionableFile {
#Id
#GeneratedValue( strategy = GenerationType.TABLE)
private long id;
....
I got issues with my model classes. For example:
#Entity
#Table(name = "kreis", catalog = "quanto_portal")
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property="idKreis")
public class Kreis implements java.io.Serializable {
private Integer idKreis;
private String kreisname;
private Set<Ort> orts = new HashSet<Ort>(0);
public Kreis() {
}
public Kreis(String kreisname) {
this.kreisname = kreisname;
}
public Kreis(String kreisname, Set<Ort> orts) {
this.kreisname = kreisname;
this.orts = orts;
}
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "idKreis", unique = true, nullable = false)
public Integer getIdKreis() {
return this.idKreis;
}
public void setIdKreis(Integer idKreis) {
this.idKreis = idKreis;
}
#Column(name = "kreisname", nullable = false, length = 50)
public String getKreisname() {
return this.kreisname;
}
public void setKreisname(String kreisname) {
this.kreisname = kreisname;
}
//#JsonManagedReference(value="kreis-ort")
#OneToMany(fetch = FetchType.LAZY, mappedBy = "kreis")
public Set<Ort> getOrts() {
return this.orts;
}
public void setOrts(Set<Ort> orts) {
this.orts = orts;
}
When I query for an "Kreis"-Object it also internally querys for the dependent "Orts", although Lazy-Loading is set. Next, in "Ort"-class a statement for dependent "Kreis"-objects is done (cause it's an attribute of Ort; Lazy-Loading is set). If "Ort" has more dependent classes/attributes for example "Persons", even the whole "Person"-class is loaded. Can anyone tell me why? Do I need to set a property in Spring or initializing a specific bean?
So far I need to ignore (with #JsonIgnoreProperties) every attribute that references to another class. I think thats wrong, cause lazy-loading should effect that dependet objects are only loaded, if I ask for it.
LAZY means lazily loaded from the database when the collection is accessed. As soon as Jackson starts serializing the object, it reads all the fields, including the orts field, which triggers the lazy loading.
If you're wanting to only serialize certain fields, then you probably want to return a projection of some sort from your controller; the just-released Spring Data Hopper M1 supports returning projections from Spring Data repositories, and you can also use Jackson projections if you need to deal with the full entity object in your controller.
I have recently upgraded Spring 2.5 to 3.2 and Hibernate 3 to 4.2.8 in a general upgrade of a web application. Most things are working now, but there is one Criteria transaction that is not working and has me puzzled. The new version returns no result (but no errors), while the old one retrieved properly the requested value.
The code is the same one in the old and new versions, and I have verified that the argument that reaches it is the same. Here is the Java code:
Criteria criteria = sessionFactory.getCurrentSession().createCriteria(ViewingResource.class);
criteria.createCriteria("viewings","currentViewings");
criteria.add(Property.forName("currentViewings.id").eq(viewingId));
ViewingResource result = (ViewingResource)criteria.uniqueResult();
ViewingResource is my entity, which is defined as follows:
#Entity
#DiscriminatorValue("viewing")
public class ViewingResource extends AbstractInformationResource {
private static final long serialVersionUID = -4569093742552159052L;
#OneToOne(targetEntity = Attribute.class, fetch = FetchType.LAZY)
#JoinColumn
private Attribute primaryAttribute;
#OneToMany(targetEntity = Viewing.class, cascade = {CascadeType.PERSIST, CascadeType.MERGE}, orphanRemoval=true)
#Cascade({org.hibernate.annotations.CascadeType.SAVE_UPDATE})
#JoinTable(name = "informationresource_viewings")
#OrderBy("sort")
private Set<ResourceViewing> viewings;
public Set<ResourceViewing> getViewings() {
return viewings;
}
public Attribute getPrimaryAttribute() {
return primaryAttribute;
}
}
As for the abstract class it extends:
#Entity
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(
name = "type",
discriminatorType = DiscriminatorType.STRING
)
#Table(name = "informationresource")
abstract public class AbstractInformationResource extends PersistentEntity<String> {
private static final long serialVersionUID = 8709376067232042462L;
#Id #GeneratedValue(generator="system-uuid")
#GenericGenerator(name="system-uuid", strategy = "uuid")
private String id;
#Column(nullable = false)
private String name;
#Column(nullable = false)
private int sort;
public String getId() {
return id;
}
public String getName() {
return name;
}
public int getSort() {
return sort;
}
}
And the original PersistentEntity is just an extension of Serializable with an id and no annotations.
I enabled Hibernate logs and found the problem may be in the way annotations work between in Hibernate 3 and 4, for the Hibernate generated SQL strings differ in this way:
Hibernate 3:
select
... (maps to all columns)
from
informationresource this_
inner join
informationresource_viewings viewings3_
on this_.id=viewings3_.informationresource_id
inner join
Viewing currentvie1_
on viewings3_.viewings_id=currentvie1_.id
where
this_.type in (
'viewing', 'directory'
)
and currentvie1_.id=?
Whereas in Hibernate 4, the generated SQL performs no joins:
select
... (maps to all columns, except type, attributeType and fieldName)
from
informationresource this_,
informationresource_viewings viewings3_,
Viewing currentvie1_
where
this_.id=viewings3_.informationresource_id
and viewings3_.viewings_id=currentvie1_.id
and this_.type='viewing'
and currentvie1_.id=?
Any hints that may help me advance with this issue? My current guess is that maybe I skipped some annotation definition that has been changed or modified since Hibernate 3, but so far I haven't been able to find anything illegal in the way I declare them - and my attempts to modify the #Join have been unsuccessful so far.
EDIT. After toying with this for some time, I have found that the issue may be related to the #DiscriminatorColumn of the abstract class. I have found that the problem lies that my type for this kind of request is never 'viewing', but 'directory'. In the old generated SQL I had both types generated:
this_.type in (
'viewing', 'directory'
)
But in the new sql this is constrained to 'viewing':
and this_.type='viewing'
I have changed in the new SQL this line, and it returns the right values that I need. The column type has only those two values, 'viewing' and 'directory'. So my question now is how to make Criteria to keep asking for the types there instead of forcing 'viewing' type.
Finally I found the solution, thanks to the hint I appointed in the EDIT block.
The solution came from establishing a formula in the base class:
#DiscriminatorFormula("case when type in ('viewing','directory') then 1 else 2 end")
And then changing in viewing resource the discriminator value annotation:
#DiscriminatorValue("1")
I really don't know why in Hibernate 3 I got all the values in the discrimination, and in Hibernate 4 the value was only this one, since the code had not changed at all. So if anyone in the future sees some similar behavior, maybe this trick can help you.
I have the following set of classes
#Embeddable
public class SharedClass {
...
private String legacyField;
...
}
#Entity
public class LongStandingEntity {
...
private SharedClass sharedClass;
...
}
Where legacyfield stores some obsolete data in older objects but is not collected for new ones. However I do need to able to continue to access the old data.
I'm creating a new entity that also makes use of SharedClass
#Entity
public class NewEntity {
...
private SharedClass sharedClass;
...
}
It doesn't need legacyField and I'd like to avoid having to add a column in the database for it whilst keeping the mapping working for LongStandingEntity.
Is this possible with JPA annotations?
I don't recall a particular annotation that can help you with that. Still I'd rather take a design approach with this situation. If you have field that belongs only to a certain class separate them in another embedabble class that's associated only with the older data.
Seems like a cleaner approach to me, just to keep entities clearly differenciated.
One workaround I have found is to override the association with the parameters insertable=false and updatable=false.
#Entity
public class NewEntity {
...
#Embedded
#AssociationOverride(name = "legacyField", joinColumns = #JoinColumn(insertable = false, updatable = false))
private SharedClass sharedClass;
...
}
I fixed the problem as following
#Entity
public class NewEntity {
...
#AttributeOverrides({
#AttributeOverride(name = "legacyField", column = #Column(insertable = false, updatable = false))
})
#Embedded
private SharedClass sharedClass;
...
}
UPDATE
but when reading entity I get the
ERROR: column new_entity_.legacyField does not exist at character 1504
so the problem is NOT fixed