How can i get only modified fields from audited entity?
When i use
AuditQuery query = getAuditReader().createQuery().forEntitiesAtRevision(MyEntity.class, revisionNumber).getResultList()
I get all fields; but i want to get only fields modified?
Without Modified Flags Feature
If you are not using the Modified Flags feature on the #Audited annotation, the only way to obtain that an audited property changed from revision X to revision Y is to actually fetch both revisions and then compare the actual field values between the two object instances yourself.
With Modified Flags Feature
Assuming you are using the Modified Flags feature on the #Audited annotation, presently the only way is to fetch the revision numbers for a given entity instance and using those revisions and prior knowledge of the audited columns, use the Envers Query API to ask whether a property changed for that revision.
Obviously this approach is not ideal as it does impose some prior knowledge on the user code's part to know the fields that are audited in order to get the desired result.
List<Number> revisions = reader.getRevisions( MyEntity.class, myEntityId );
for ( Number revisionNumber : revisions ) {
for ( String propertyName : propertyNamesToCheckList ) {
final Long hits = reader.createQuery()
.forRevisionsOfEntity( MyEntity.class, false, false )
.add( AuditEntity.id().eq( myEntityId ) )
.add( AuditEntity.revisionNumber().eq( revisionNumber ) )
.add( AuditEntity.propertyName( propertyName ).hasChanged() )
.addProjection( AuditEntity.id().count() )
.getSingleResult();
if ( hits == 1 ) {
// propertyName changed at revisionNumber
}
else {
// propertyName didn't change at revisionNumber
}
}
}
Modified Flags Property Changes Queries
In Hibernate Envers 6.0, we are introducing a new query that combines forRevisionsOfEntity with the modified flags query mechanism to obtain not only the revised instances for a given entity class type and primary key, but also a list of fields that were modified at each revision.
The following pseudo code gives an example of the future API:
List results = reader.forRevisionsOfEntityWithChanges( MyEntity.class false )
.add( AuditEntity.id().eq( entityId ) )
.getResultList();
Object previousEntity = null;
for ( Object row : results ) {
Object[] rowArray = (Object[]) row;
final MyEntity entity = rowArray[0];
final RevisionType revisionType = (RevisionType) rowArray[2];
final Set<String> propertiesChanged = (Set<String>) rowArray[3];
for ( String propertyName : propertiesChanged ) {
// using the property name here you know
// 1. that the property changed in this revision (no compare needed)
// 2. Can get old/new values easily from previousEntity and entity
}
}
This feature may be expanded upon or changed as it is going to be considered experimental, but it is something that users have asked for and we at least intend to deliver a first pass at this functionality based on modified flags.
We haven't decided if or how we'd support this for non-modified flags at the moment, so again the only choice there will presently be a brute force bean comparison.
Fore more details on this feature see HHH-8058.
Related
We use the https://github.com/etiennestuder/gradle-jooq-plugin for generating jOOQ classes on-demand from our database (DDL, .sql file with CREATE TABLE statements). However, we noted that when overriding the strategy.name in jooq.configurations.main.generationTool.generator, we started seeing peculiar behavior.
The database schema
We have some field names like _is_tombstone, _revision etc in our tables. These are for "metadata" which differs from the normal "data" columns (data coming from another system vs "metadata" logging event details about why the change was triggered).
Other tables like id, name etc do not have any special prefix.
The generator
Here is our current generator strategy, inspired by https://www.jooq.org/doc/latest/manual/code-generation/codegen-generatorstrategy/
public class CustomNameGeneratorStrategy extends DefaultGeneratorStrategy {
#Override
public String getJavaMemberName( Definition definition, Mode mode ) {
String memberName = super.getJavaMemberName( definition, mode );
// Converts e.g. _IsTombstone to _isTombstone
if ( memberName.startsWith( "_" ) ) {
memberName = "" + memberName.charAt( 0 ) + toLowerCase( memberName.charAt( 1 ) ) + memberName.substring( 2 );
}
return memberName;
}
#Override
public String getJavaGetterName( Definition definition, Mode mode ) {
String methodName = super.getJavaGetterName( definition, mode );
methodName = methodName.replace( "_", "" );
// isTombstone() seems more natural than getIsTombstone()
methodName = methodName.replace( "getIs", "is" );
return methodName;
}
#Override
public String getJavaSetterName( Definition definition, Mode mode ) {
String methodName = super.getJavaSetterName( definition, mode );
return methodName.replace( "_", "" );
}
}
The problem
With the above in place, the code below causes some very nasty bugs with certain fields not copied from the POJO to the Record instance. Only id, name etc works. All fields which have underscores in the name get excluded in the copying; they have null values in the target Record instance.
This, in turns, makes the DB insertion fail since certain mandatory fields do not have any values.
import static some.package.Tables.GROUPS;
import some.package.tables.records.GroupRecord;
import some.package.tables.pojos.TSDBGroup;
// ...
TSDBGroup tsdbGroup = createTsdbGroup(...);
// The GroupRecord here becomes a "partial copy".
GroupRecord groupRecord = create.newRecord(GROUPS, tsdbGroup);
groupRecord.store();
Why does this happen?
The problem turned out to be that jOOQ expects one of the following to hold true, unless you annotate your getters/setters with JPA-style annotations:
Method names for field setters use the standard name (setFoo for a DB column named foo or FOO), or
Field names use the standard name (foo for a DB column named foo or FOO)
Break both of these rules, and you keep to keep the pieces. :-)
More specifically, this is caused by the following code in jOOQ:
// No annotations are present
else {
members = getMatchingMembers(configuration, type, field.getName(), true);
method = getMatchingGetter(configuration, type, field.getName(), true);
}
// Use only the first applicable method or member
if (method != null)
Tools.setValue(record, field, method.invoke(source));
else if (members.size() > 0)
setValue(record, source, members.get(0), field);
}
Workaround
The simple workaround is to remove the getJavaMemberName method in the custom GeneratorStrategy. jOOQ is then able to populate the fields as expected when creating Records from POJOs.
we are currently upgrading to Hibernate 5.4.29.
We have some custom Usertypes that map for example bigint to a custom usertype (extends Usertype).
metadata.applyBasicType(new MyUserType(), new String[] { MyUserType.class.getName()});
If I create a native query with the new version e.g.
hibernate.createNativeQuery(select * from tablexy);
and one of the columns contains bigint values it tries to map the type to the custom Usertype MyUserType.
This seems to be happening in the class JdbcResultMetadata
//Get the contributed Hibernate Type first
Set<String> hibernateTypeNames = factory.getMetamodel()
.getTypeConfiguration()
.getJdbcToHibernateTypeContributionMap()
.get( columnType );
//If the user has not supplied any JDBC Type to Hibernate Type mapping, use the Dialect-based mapping
if ( hibernateTypeNames != null && !hibernateTypeNames.isEmpty() ) {
if ( hibernateTypeNames.size() > 1 ) {
throw new HibernateException(
String.format(
"There are multiple Hibernate types: [%s] registered for the [%d] JDBC type code",
String.join( ", ", hibernateTypeNames ),
columnType
) );
}
else {
hibernateTypeName = hibernateTypeNames.iterator().next();
}
}
else {
hibernateTypeName = factory.getDialect().getHibernateTypeName(
columnType,
length,
precision,
scale
);
}
return factory.getTypeResolver().heuristicType(
hibernateTypeName
);
If we use the native query without adding a scalar we get:
org.hibernate.HibernateException: There are multiple Hibernate types: [MyUserType1, MyUserType2] registered for the [-5] JDBC type code
This can be prevented if I add addScalar() for every column. Which is quite cumbersome. The old hibernate version did not map to the usertypes unless we explicitly added e.g. a ResultTransformer.
Do we have to add addScalar for every column if we don't want mapping to usertypes or is there some other way to prevent this. (Basically I want a native query where the results are not mapped to the usertypes without adding addScalar for every column)
You could create a custom org.hibernate.boot.spi.MetadataContributor or org.hibernate.integrator.spi.Integrator (register it through the service loader mechanism) and remove type mappings that you don't want by altering the map metadataCollector.getBootstrapContext().getTypeConfiguration().getJdbcToHibernateTypeContributionMap() or sessionFactory.getMetamodel().getTypeConfiguration().getJdbcToHibernateTypeContributionMap()
I actually think this might be a bug, but I'm not sure at all.
I have set up a unidirectional OneToMany relationship like the example in section 2.10.5.1 of the JPA 2.1 spec:
#Entity
public class Client implements Serializable {
...
#OneToMany
private List<ServiceOrder> activeServiceOrders;
public void setActiveServiceOrders( List<ServiceOrder> activeServiceOrders ) {
this.activeServiceOrders = activeServiceOrders;
}
public List<ServiceOrder> getActiveServiceOrders() {
return activeServiceOrders;
}
}
The ServiceOrder class implements hashCode and equals using its auto-generated long id. They were implemented by Eclipse.
public class ServiceOrder implements Serializable {
#TableGenerator( name = "generator_serviceOrder", table = "SEQUENCE_TABLE", pkColumnName = "SEQ_NAME", valueColumnName = "LAST_VALUE_GEN", pkColumnValue = "SERVICE_ORDER_SEQ", allocationSize = 1, initialValue = 0 )
#Id
#GeneratedValue( strategy = GenerationType.TABLE, generator = "generator_serviceOrder" )
private long id;
...
#Override
public boolean equals( Object obj ) {
if ( this == obj )
return true;
if ( obj == null )
return false;
if ( getClass() != obj.getClass() )
return false;
ServiceOrder other = (ServiceOrder ) obj;
if ( id != other.id )
return false;
return true;
}
...
}
Tables are all auto-generated as expected. Then, when I want to establish the relationship I do:
...
Client client = entityManager.find(...);
ServiceOrder so = entityManager.find(...);
client.getActiveServiceOrders().add( so );
...
Everything is fine until now, transaction commits successfully. Problem starts when I try to remove the relationship (in another transaction, another moment):
...
Client sameClient = entityManager.find(...);
ServiceOrder sameSo = entityManager.find(...);
log.info(sameClient.getActiveServiceOrders().size()); // "1", OK
log.info(sameClient.getActiveServiceOrders().contains(so)); // "false". Why?
sameClient.getActiveServiceOrders().remove(so); // does nothing, returns false
...
I debugged and discovered that the following is failing in ServiceOrder.equals():
...
if ( getClass() != obj.getClass() ) // different probably because JPA (Hibernate) proxies one of the objects
return false; // returns
...
I found two temporary solutions:
Remove ServiceOrder equals() and hashCode();
or
Make the relationship bidirectional (and of course update both sides every add/remove);
I don't understand this behavior. Why the difference in treatment if the relationship is uni or bi-directional? Also, if I get these entities in the context of the same transaction, how would fail the first equals test:
if ( this == obj )
return true;
I'm using JPA 2.1 (Wildfly 8.1.0).
Best Regards and thank you in advance.
Renan
You should override equals and hashCode but you should never use the ID for hash code unless you make the hashCode immutable and use the ID only when it's not null for equality.
Otherwise, prior to saving an Entity with the ID being null which is to be assigned during the flush time when you add a Transient entity to a collection, the moment it gets persisted and the ID is generated the equals/hashCode contract is going to broken.
Hibernate best practices suggest using a business key for object equality/hashCode.
So quoting the reference documentation:
The general contract is: if you want to store an object in a List, Map
or a Set then it is a requirement that equals and hashCode are
implemented so they obey the standard contract as specified in the
documentation.
To avoid this problem we recommend using the "semi"-unique attributes
of your persistent class to implement equals() (and hashCode()).
Basically you should think of your database identifier as not having
business meaning at all (remember, surrogate identifier attributes and
automatically generated values are recommended anyway). The database
identifier property should only be an object identifier, and basically
should be used by Hibernate only. Of course, you may also use the
database identifier as a convenient read-only handle, e.g. to build
links in web applications.
Instead of using the database identifier for the equality
comparison, you should use a set of properties for equals() that
identify your individual objects. For example, if you have an "Item"
class and it has a "name" String and "created" Date, I can use both to
implement a good equals() method. No need to use the persistent
identifier, the so-called "business key" is much better. It's a
natural key, but this time there is nothing wrong with using it!
Don't override the equals and hashCode. Hibernate has its own implementation to find out the objects, and that's why you don't get the expected result.
This article explains more:
https://community.jboss.org/wiki/EqualsandHashCode?_sscc=t
I have an object with 70 attributes. For ease of use I created 2 objects, a 'main' object and a 'details' object, with 1:1 relationship based on an auto-generated integer ID. I had a SEARCH screen that allowed searching on any of the main attributes, for which I build Restriction objects for whatever the user typed in. What was nice was that I did this all through iterating through the fields and building criterion - I didn't need ugly code to specifically handle each of the 30 attributes.
Now they want to search on the details fields as well. My previous screen-field-iterating code works perfectly with no changes (the whole reason for making it 'generic'), however I cannot get the JOIN to work to query on details fields.
class House {
Integer houseID;
String address;
. . .
HouseDetails houseDetails;
}
class HouseDetails {
Integer houseID;
String color;
. . .
}
I tried to create an alias and add it to the criteria :
criteria.createAlias("houseDetails", "houseDetails");
but I get this error :
org.hibernate.QueryException: could not resolve property: color of: House
Here's the thing - I know this would work if I prefix my restrictions with the alias name, but I do NOT want to have to know which table (House or HouseDetails) the field comes from. That would ruin all the automatic looping code and create specific code for each field.
Since SQL can do this as long as the column names are unique :
select * from house, housedetails where house.houseID = housedetails.houseID
and color = 'blue';
I'm wondering how can I get this to work using criteria??
As an aside, but related to this : Is there a way to perform something like Java's introspection on Hibernate HBM.XML mapping files? A number of times I've wanted to do this to solve problems but never found an answer. For the above problem, if I could easily find out which table contained each field, I could add the prefix to the Restriction. Something like this :
// Map of search keys (columns) to searching values
for ( String key : parms.keySet() ) {
String val = parms.get(key);
if ( HIBERNATE-SAYS-KEY-IS-FROM-DETAILS-TABLE ) {
key = "houseDetails." + key;
}
criteria.add(Restrictions.eq(key,val));
}
You can make method to find table name for passed column name.
By using SessionFactory.getClassMetaData() you can get all the information about that class. Once you have ClassMetaData then you can get all the property names. An demo method is shown below:
public String findTableName(String columnName)
{
boolean found=false;
Map<String, ClassMetadata> classMetaData = sessionFactory.getAllClassMetadata();
for (Entry<String, ClassMetadata> metaData : classMetaData.entrySet())
{
String[] propertyNames = metaData.getValue().getPropertyNames();
for (String property : propertyNames)
{
if(property == columnName)
{
return metaData.getKey() + "." + property;
found=true;
break;
}
}
if(found)
break;
}
}
The alias mechanism in hibernate and the Criteria API is pretty well specified. I suggest going through the documentation a little a bit.
I think what you want is something like this:
Criteria criteria = session.createCriteria(House.class);
criteria.createAlias("houseDetails.color", "houseColor");
criteria.add(Restrictions.eq("houseColor", "red"));
I've been pulling my hair out over this for the best part of a day now, and simply can't find any answers to this problem.
I've got a PostgreSQL schems that looks like this:
+---------+ 1-n +-------------+ 1-1 +------+
| Product |-------->| ProductSpec |-------->| Spec |
+---------+ +-------------+ +------+
This represents a one to many relationship between a Product and its list of Specifications (The reason I don't just use a foreign key in the specifications table into the products table is because specifications can belong to things that aren't in the product inheritance tree, those links are represented by other intersection tables).
Each Specification is a subclass of a Specification class (Weight, Length, NumberOfThings, and so on), with the name of the subclass in question being stored in the Spec table. Each product has a collection of specifications, but each subclass of specification can only appear once. A product can only have one weight (though if you need a weight for the actual product, and a shipping weight for the courier to calculate shipping charges, you can simply subclass ActualWeight and ShippingWeight from the Weight specification).
Using the simplest case, a Set in the Product class, I'm able to construct the object graph correctly from a Hibernate query of the products table. I want to use a Map instead, however, so I can address specific specifications directly. The plan was to use the class name as the key, but I'm having serious issues trying to get it to work. I'm unable to figure out how to use the Java class name as the key, and trying to use the class name as stored in the database as the map key is proving problematic.
As currently implemented, I'm able to query the specifications, and the products individually (if I comment out the code implementing the mapping between products and specifications). I can also query the products with the specifications embedded if I use a set, but if I use a map with the MapKey set to be the specifications class name, I get an exception.
Sep 01, 2013 1:25:55 AM org.hibernate.util.JDBCExceptionReporter
logExceptions WARNING: SQL Error: 0, SQLState: 42P01 Sep 01, 2013
1:25:55 AM org.hibernate.util.JDBCExceptionReporter logExceptions
SEVERE: ERROR: relation "specifications" does not exist Position: 424
I've annotated my (cut down) classes as follows. The product class:
#Entity
#Table (
name="products",
schema="sellable"
)
public abstract class Product extends Sellable {
private Map <String, Specification> specifications = new HashMap <> ();
#OneToMany (fetch = FetchType.EAGER)
#Cascade (CascadeType.SAVE_UPDATE)
#JoinTable (
schema = "sellable",
name = "productspecifications",
joinColumns = {#JoinColumn (name = "sll_id")},
inverseJoinColumns = {#JoinColumn (name = "spc_id")})
#MapKey (name = "className")
private Map <String, Specification> getSpecifications () {
return this.specifications;
}
private Product setSpecifications (Map <String, Specification> specs) {
this.specifications = specs;
return this;
}
}
And the Specification class:
#Entity
#Table (
name="specifications",
schema="sellable",
uniqueConstraints = #UniqueConstraint (columnNames="spc_id")
)
#Inheritance (strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn (name = "spc_classname", discriminatorType=DiscriminatorType.STRING)
public abstract class Specification implements Serializable {
private Integer specId = null;
private String className = null;
#Id
#Column (name="spc_id", unique=true, nullable=false)
#SequenceGenerator (name = "specifications_spc_id_seq", sequenceName = "sellable.specifications_spc_id_seq", allocationSize = 1)
#GeneratedValue (strategy = GenerationType.SEQUENCE, generator = "specifications_spc_id_seq")
public Integer getSpecId () {
return this.specId;
}
private Specification setSpecId (Integer specId) {
this.specId = specId;
return this;
}
#Column (name="spc_classname", insertable = false, updatable = false, nullable = false)
public String getClassName () {
return this.className;
}
private void setClassName (String className) {
this.className = className;
}
}
The DB schema looks like this:
CREATE TABLE sellable.sellables
(
sll_id serial NOT NULL, -- Sellable ID
sll_date_created timestamp with time zone NOT NULL DEFAULT now(), -- Date the item was created
sll_date_updated timestamp with time zone NOT NULL DEFAULT now(), -- Date the item was last updated
sll_title character varying(255) NOT NULL, -- Title of the item
sll_desc text NOT NULL, -- Textual description of the item
CONSTRAINT sellables_pkey PRIMARY KEY (sll_id)
)
CREATE TABLE sellable.products
(
sll_id integer NOT NULL, -- Sellable ID
mfr_id integer NOT NULL, -- ID of the product manufacturer
CONSTRAINT products_pkey PRIMARY KEY (sll_id),
CONSTRAINT products_mfr_id_fkey FOREIGN KEY (mfr_id)
REFERENCES sellable.manufacturers (mfr_id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION,
CONSTRAINT products_sll_id_fkey FOREIGN KEY (sll_id)
REFERENCES sellable.sellables (sll_id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
)
CREATE TABLE sellable.specifications
(
spc_id serial NOT NULL, -- Specification ID
spc_classname character varying(127) NOT NULL, -- Specification subclass
CONSTRAINT specifications_pkey PRIMARY KEY (spc_id)
)
CREATE TABLE sellable.productspecifications
(
ps_id serial NOT NULL, -- Primary key
sll_id integer NOT NULL, -- Product the specification is linked to
spc_id integer NOT NULL, -- Specification the product is associated with
CONSTRAINT productspecifications_pkey PRIMARY KEY (ps_id),
CONSTRAINT productspecifications_sll_id_fkey FOREIGN KEY (sll_id)
REFERENCES sellable.products (sll_id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION,
CONSTRAINT productspecifications_spc_id_fkey FOREIGN KEY (spc_id)
REFERENCES sellable.specifications (spc_id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION,
CONSTRAINT productspecifications_spc_id_key UNIQUE (spc_id)
)
The query that Hibernate generates is listed below (I've not trimmed this the way I have the classes in case there's something in the unabridged query that is an issue). One obvious problem is that it's trying to query the specifications table without inserting the schema name.
select
bicycle0_.sll_id as sll1_0_3_,
bicycle0_2_.sll_date_created as sll2_0_3_,
bicycle0_2_.sll_date_updated as sll3_0_3_,
bicycle0_2_.sll_desc as sll4_0_3_,
bicycle0_2_.sll_title as sll5_0_3_,
bicycle0_1_.mfr_id as mfr2_1_3_,
bicycle0_.btp_id as btp2_2_3_,
manufactur1_.mfr_id as mfr1_4_0_,
manufactur1_.mfr_name as mfr2_4_0_,
specificat2_.sll_id as sll1_5_,
specificat3_.spc_id as spc2_5_,
(select
a9.spc_classname
from
specifications a9
where
a9.spc_id=specificat2_.spc_id) as formula0_5_,
specificat3_.spc_id as spc2_5_1_,
specificat3_.spc_classname as spc1_5_1_,
specificat3_1_.dec_value as dec1_6_1_,
specificat3_2_.bol_value as bol1_7_1_,
specificat3_3_.int_value as int1_8_1_,
specificat3_4_.str_value as str1_9_1_,
bicycletyp4_.btp_id as btp1_3_2_,
bicycletyp4_.btp_name as btp2_3_2_
from
sellable.bicycles bicycle0_
inner join
sellable.products bicycle0_1_
on bicycle0_.sll_id=bicycle0_1_.sll_id
inner join
sellable.sellables bicycle0_2_
on bicycle0_.sll_id=bicycle0_2_.sll_id
left outer join
sellable.manufacturers manufactur1_
on bicycle0_1_.mfr_id=manufactur1_.mfr_id
left outer join
sellable.productspecifications specificat2_
on bicycle0_.sll_id=specificat2_.sll_id
left outer join
sellable.specifications specificat3_
on specificat2_.spc_id=specificat3_.spc_id
left outer join
sellable.specdecimalvalues specificat3_1_
on specificat3_.spc_id=specificat3_1_.spc_id
left outer join
sellable.specbooleanvalues specificat3_2_
on specificat3_.spc_id=specificat3_2_.spc_id
left outer join
sellable.specintegervalues specificat3_3_
on specificat3_.spc_id=specificat3_3_.spc_id
left outer join
sellable.specstringvalues specificat3_4_
on specificat3_.spc_id=specificat3_4_.spc_id
left outer join
sellable.bicycletypes bicycletyp4_
on bicycle0_.btp_id=bicycletyp4_.btp_id
where
bicycle0_.sll_id=?
The problem is in the sub-query, which isn't getting a schema prepended to the specifications table name.
If anyone knows how to either get the query to be correct, or of using the class name directly as the Java map key, I'd appreciate being told.
EDIT: The reason I want to use a map instead of a set is because I want to directly address items in the specifications collection. If I use sets, the queries generated by Hibernate work, but I don't have an index to access the elements by. The API of the Product object hides the fact that the specifications are stored in a collection and provides getters and setters for each individual specification.
If I make the specifications a set, I have to implement the getters and setters like this:
#Transient
public BigDecimal getActualWeight () {
BigDecimal found = null;
for (Specification spec : this.specifications) {
if (spec instanceof ActualWeightSpec) {
found = ((ActualWeightSpec) spec).getValue ();
break;
}
}
return found;
}
public Product setActualWeight (Number value) {
ActualWeightSpec newWeight = new ActualWeightSpec ();
newWeight.setValue (value);
for (Specification spec : this.specifications) {
if (spec instanceof ActualWeightSpec) {
((ActualWeightSpec) spec).setValue (value);
return this;
}
}
this.specifications.add (newWeight);
return this;
}
Having to iterate over a set to get individual specification records seems a really bad way of accessing those records directly.
I did try maintaining a hashmap internally, and having the getter and setter for the specifications accept and return sets with a conversion taking place in the getter and setter. That way I'd only have to take the hit of iterating the specifications once.
private Product setSpecifications (Set <Specification> specs) {
HashMap <String, Specification> specsMap = new HashMap <> ();
for (Specification spec : specs) {
specsMap.put(spec.getClassName (), spec);
}
this.specifications = specsMap;
return this;
}
This didn't work either, causing Hibernate to throw an exception.
SEVERE: illegal access to loading collection
You could use a internal map of specifications, not bothering the database with the map. Do not initialize the map in getters or setters Hibernate uses, but check in your getters (e.g. getActualWeight) if your transient map has been initialized already. If not, iterate the specs once and build the map. Btw, if there are not too many specs, iterating each time should not hurt too much.