I have the following class:
#Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED)
public class Yard extends ModelObject {
// Relations
#ManyToOne(optional=false)
#JoinColumn(name = "house_id", foreignKey=#ForeignKey(name="fk1_yard"))
#Getter #Setter
#JsonView({Views.AdminPortal.class})
private House house = null;
}
And I get the following error
Could not write JSON: Unable to find com.db.model.main.House with id 7
My understanding was that targetAuditMode = RelationTargetAuditMode.NOT_AUDITED would prevent this error by only auditing the id number. What am I doing wrong here?
Your usage is incorrect. I believe what you're looking for is:
#Audited
#Entity
public class Yard extends ModelObject {
#ManyToOne(optional=false)
#JoinColumn(name = "house_id", foreignKey=#ForeignKey(name="fk1_yard"))
#Getter
#Setter
#JsonView({Views.AdminPortal.class})
#Audited(targetAuditMode = RElationTargetAuditMode.NOT_AUDITED)
private House house = null;
}
You'll notice how the #Audited annotation with targetAuditMode is being used on the association in the entity mapping rather than on the class-mapping. That attribute has no impact at the class-level.
Related
I have a class Project, a superclass Resource with two subclasses StorageResource and ComputeResource. I have defined a Hibernate mapping (I am using Hibernate version 5.3.7.Final) as follows:
#Entity
#Table(name = "PROJECTS", ...})
public class Project implements Serializable {
...
#OneToMany(
mappedBy = "project",
cascade = CascadeType.ALL,
orphanRemoval = true
)
private List<Resource> resources;
...
}
#Entity
#Table(name = "resources")
#Inheritance(strategy = InheritanceType.JOINED)
public class Resource implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private long id;
#ManyToOne
private Project project;
...
}
#Entity
#Table(name = "storage_resources")
#PrimaryKeyJoinColumn(name = "id")
public class StorageResource extends Resource implements Serializable {
...
}
#Entity
#Table(name = "compute_resources")
#PrimaryKeyJoinColumn(name = "id")
public class ComputeResource extends Resource implements Serializable {
...
}
My Java code can save instances of both subclasses (StorageResource and ComputeResource) correctly to the database. However, when I load these records by loading a project and its resources, I have the following:
Storage resource records are loaded as Java objects that are instances of StorageResource,
Compute resource records are loaded as Java objects that are instances of Resource, not of ComputeResource as I would expect.
I have spent the last two days trying to figure out why this is happening: the mappings of both sub-classes are the same. Am I missing something?
EDIT
I found the problem. It was quite misleading, because there was no error message. The environment in which the bug occurred had an outdated configuration file. The Hibernate mapping for ComputeResource
<mapping class="....ComputeResource"/>
was missing. After adding this line the mapping is working as expected.
We have 2 #Entitys having fields like that:
#Data
#NoArgsConstructor
#Entity
#Audited
#AuditTable(schema = "audit", value = "tariff_option")
#AuditOverride(forClass = BaseEntity.class, isAudited = true)
#Table(name = "tariff_option")
#EqualsAndHashCode(of = {}, callSuper = true)
#ToString()
public class TariffOption extends BaseEntity {
private static final long serialVersionUID = -6398231779406280786L;
...
#ManyToOne
#JoinColumn(name = "dict_tariff_id", updatable = false)
private DictTariff tariff;
}
and
#Data
#NoArgsConstructor
#Entity
#Audited
#AuditTable(schema = "audit", value = "dict_tariff")
#AuditOverride(forClass = BaseEntity.class, isAudited = true)
#Table(name = "dict_tariff")
#EqualsAndHashCode(of = {}, callSuper = true)
#ToString(exclude = {"contractorTypes", "service", "tariffOptions", "dictTariffOptions"})
#JsonIgnoreProperties(value = {"contractorTypes", "service", "tariffOptions", "dictTariffOptions"})
public class DictTariff extends BaseEntity {
private static final long serialVersionUID = -3881580795280130829L;
...
#OneToMany(mappedBy = "tariff", fetch = FetchType.LAZY)
private List<TariffOption> tariffOptions;
}
and then when we save the dictTariffOption variable of TariffOption class, the associated dictTariff of DictTariff class is saved because of #ManyToOne annotation and ownership of the tie by the dictTariffOption:
repository.save(dictTariffOption)
This causes problem that on any change of dictTariffOption the dictTariff is saved again with the same values. That causes the new entry in audit scheme what we want to avoid.
I tried EntityManager.detach(dictTariff) right before saving the dictTariffOption, but it doesn't help. So what is the approach to ignore the update of the linked entity? I also tried to re-retireve it right before the save to make it not to be JPA-dirty, but it doesn't help and it got updated.
jpa:
generate-ddl: false
hibernate:
ddl-auto: validate
properties:
hibernate:
dialect: rf.dom.billing.model.common.postgres.CustomPostgreSqlDialect
enable_lazy_load_no_trans: true
org:
hibernate:
envers:
default_schema: audit
Per Chris,
public class DictTariff extends BaseEntity {
#OneToMany(mappedBy = "tariff", fetch = FetchType.LAZY)
#NotAudited
private List<TariffOption> tariffOptions;
helped and no longer update the audit changes on DictTariff when whe TariffOption, reffering the DictTariff is saved.
I translated some hbm configuration to annotated java class. In the hbm some classes were defined with inheritance strategy "SINGLE_TABLE" and some other entity refer to it with many to one relationship as Map.
when I try to lauch the application I get the following error :
Caused by: org.hibernate.AnnotationException: Map key property not found: com.package.MyClass.Id
I searched for some explanation online, but nothing describing at the same time the SINGLE_TABLE inheritance strategy and the OneToMany behavior in this case.
I have the parent class as follows :
#Entity
#Table(name = "parentclass")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "type", length = 10, discriminatorType = DiscriminatorType.INTEGER)
#DiscriminatorValue("100")
public abstract class ParentClass {
#Id
#Column(name = "Id", length = 11)
#GeneratedValue(strategy=GenerationType.AUTO)
private Integer id;
....
}
the child class :
#Entity
#DiscriminatorValue("2")
public abstract class ChildClass {
....
}
the class with the relation :
#Entity
#Table(name = "otherclass")
#PrimaryKeyJoinColumn(name = "IdSys")
public class OtherClass extends OtherParent {
....
#OneToMany
#JoinColumn(name = "IdOther")
#MapKey(name = "Id")
#Where(clause = "type = 2")
private Map<String, ChildClass> childClassMap;
....
}
It worked when it was defined in hbm so I guess it should work with annotation.
I finally find out what was the issue.
In hbm file, the MapKey name refer to the column name. But the annotation refer to the field name.
So instead of
#MapKey(name = "Id")
I must have
#MapKey(name = "id")
I have a two objects with simple #OneToMany relationship which looks as follows:
parent:
#Entity
public class ParentAccount {
#Id
#GeneratedValue
private long id;
private String name;
#OneToMany(fetch = FetchType.EAGER, mappedBy = "parentAccount")
private Set<LinkedAccount> linkedAccounts;
}
child:
#Entity
public class LinkedAccount {
#Id
#GeneratedValue
private long id;
#ManyToOne(optional = false)
private ParentAccount parentAccount;
private String name;
// empty constructor for JPA
public LinkedAccount() {
}
}
I ma using Spring CrudRepository to operate with these entities. However, when calling ParentAccount parent = parentAccountRepository.findOne(id);, some kind of infinite loop starts happening and hibernate spams this all over the console:
Hibernate: select linkedacco0_.parent_account_id as parent_a6_1_0_, linkedacco0_.id as id1_0_0_, linkedacco0_.id as id1_0_1_, linkedacco0_.aws_id as aws_id2_0_1_, linkedacco0_.key_id as key_id3_0_1_, linkedacco0_.name as name4_0_1_, linkedacco0_.parent_account_id as parent_a6_0_1_, linkedacco0_.secret_key as secret_k5_0_1_ from linked_account linkedacco0_ where linkedacco0_.parent_account_id=?
I tried changed the fetch type to LAZY but then I get this error:
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.berrycloud.scheduler.model.ParentAccount.linkedAccounts, could not initialize proxy - no Session
(It seems that it is trying to do the lazy load outside of the transactional context).
This is my CRUD repository:
#Repository
public interface ParentAccountRepository extends CrudRepository<ParentAccount, Long> {
}
Could someone tell me how to resolve this issue? I would prefer the solution with EAGER fetch. Thank you for any tips
EDIT: here is the schema I am using
CREATE TABLE parent_account (
id BIGINT auto_increment,
name VARCHAR(80) null,
PRIMARY KEY (`id`)
);
CREATE TABLE linked_account (
id BIGINT auto_increment,
parent_account_id BIGINT,
name VARCHAR(80) null,
FOREIGN KEY (`parent_account_id`) REFERENCES `parent_account` (`id`),
PRIMARY KEY (`id`)
);
As the first answer suggests:
Do not use Lombok's #Data annotation on #Entity classes.
Reason: #Data generates hashcode(), equals() and toString() methods that use the generated getters. Using the getter means of course fetching new data even if the property was marked with FetchType=LAZY.
Somewhere along the way hibernate tries to log the data with toString() and it crashes.
Problem solved. I was using a custom #toString method in the LinkedAccount which was referencing the ParentAccount. I had no idea that this could cause any problem and therefor I did not include the toString in my question.
Apparently, this was causing an infinite loop of lazy loading and removing this reference fixed the problem.
As user1819111 told, #Data from Lombok is not compatible with #Entity and FetchType=LAZY. I had used Lombok.Data (#Data) and I was getting this error.
As I don't want do create all get/set, I just put the Lombok #Setter and #Getter in your class and all will work fine.
#Setter
#Getter
#Entity
#Table(name = "file")
#SequenceGenerator(name = "File_Sequence", allocationSize=1, sequenceName = "file_id_seq")
public class MyClass{
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "File_Sequence")
#Column(name = "id")
private Long id;
#Column(name = "name")
private String name;
#OneToMany(mappedBy = "file", cascade = CascadeType.DETACH, fetch = FetchType.LAZY)
private Set<Base2FileDetail> details = new HashSet<>();
}
Something like this does not work?
#Entity
public class Account {
#Id
#GeneratedValue
private long id;
private String name;
#ManyToOne(cascade={CascadeType.ALL})
#JoinColumn(name="manager_id")
private Account manager;
#OneToMany((fetch = FetchType.EAGER, mappedBy="manager")
private Set<Account> linkedAccounts = new HashSet<Account>();
}
I recently had this issue due to a poorly defined Jackson2HttpMessageConverter.
I had done something like the following.
#Bean
RestTemplate restTemplate(#Qualifier("halJacksonHttpMessageConverter")
TypeConstrainedMappingJackson2HttpMessageConverter halConverter) {
final RestTemplate template = new RestTemplateBuilder().build();
halConverter.setSupportedMediaTypes(List.of(/* some media types */));
final List<HttpMessageConverter<?>> converters = template.getMessageConverters();
converters.add(halConverter);
template.setMessageConverters(converters);
return template;
}
This caused a problem because the media types did not include all the defaults. Changing it to the following fixed the issue for me.
halConverter.setSupportedMediaTypes(
new ImmutableList.Builder<MediaType>()
.addAll(halConverter.getSupportedMediaTypes())
.add(/* my custom media type */)
.build()
);
This simple way worked for me. Just use JsonIgnoreProperties .
#JsonIgnoreProperties(value = {"linkedAccounts"})
#ManyToOne(cascade = { CascadeType.PERSIST})
#JoinColumn(name = "abc", referencedColumnName = "abc")
private ParentAccount parentAccount;
This way worked for me without removing #ToSring annotation:
#Entity
#Getter
#Setter
#ToString
#RequiredArgsConstructor
#AllArgsConstructor
#Table(name = "parent_accounts")
public class ParentAccount {
#JsonIgnoreProperties({"parentAccount"})
#OneToMany(mappedBy = "parentAccount",
cascade = CascadeType.ALL,
orphanRemoval = true)
private List<LinkedAccount> linkedAcounts;
// ...
}
#Entity
#Getter
#Setter
#ToString
#RequiredArgsConstructor
#AllArgsConstructor
#Table(name = "linked_accounts")
public class LinkedAccount {
#JsonIgnoreProperties("linkedAcounts")
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "parentAccount_id")
private ParentAccount parentAccount;
// ...
}
PS: In #JsonIgnoreProperties You can also ignore more than one field for preventing infinite loop
I've ran into problem with composite primary key handling by Hibernate as a JPA provider.
My entities look like below
// Entity class
#Entity
#IdClass(ExternalMatchPK.class)
#Table(name = "external_match")
public class ExternalMatch {
#Id
#Column(name = "place_id")
private Integer placeId;
#Id
#Column(name = "external_object_id")
private Integer externalObjectId;
// ... Other stuff here
}
// Key class
public class ExternalMatchPK implements Serializable {
private Integer placeId;
private Integer externalObjectId;
}
Looks pretty simple yet no matter what I do I keep getting the following exception (lines are splitted for readability):
org.hibernate.MappingException:
Repeated column in mapping for entity: ExternalMatch
column: external_object_id (should be mapped with insert="false" update="false")
I've tried placing annotation on entity class fields and key class fields together as well as separately, moving all annotations from fields to getters on each one of the classes, using key calss as #Embeddable and putting it into the entity class with #EmbeddedId. Nothing seems to work.
This case seems trivial so maybe it's something wrong with our setup but I can't even imagine where to look for the issue.
Any advice is much appreciated.
It appears that I shot myself in the foot with this.
The issue was that I had a biderectional mapping between ExternalMatch and ExternalObject I forgot about trying to replace the actual entity with its integer id.
So changing
// Entity class
#Entity
#IdClass(ExternalMatchPK.class)
#Table(name = "external_match")
public class ExternalMatch {
#Id
#Column(name = "place_id")
private Integer placeId;
#Id
#Column(name = "external_object_id")
private Integer externalObjectId;
// ... Other stuff here
}
// Key class
public class ExternalMatchPK implements Serializable {
private Integer placeId;
private Integer externalObjectId;
}
// Related entity class
#Entity
#Table(name = "external_object")
public class ExternalObject extends AbstractNameableEntity {
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "external_object_id", nullable = false)
private List<ExternalMatch> matches;
// ...
}
to reprsent actual mappings like this
// Entity class
#Entity
#IdClass(ExternalMatchPK.class)
#Table(name = "external_match")
public class ExternalMatch {
#Id
#ManyToOne
#JoinColumn(name = "external_object_id", referencedColumnName = "id")
private ExternalObject externalObject;
#Id
#ManyToOne
#JoinColumn(name = "place_id")
private Poi place;
// ... Other stuff here
}
// Key class
public class ExternalMatchPK implements Serializable {
private Poi place;
private ExternalObject externalObject;
}
// Related entity class
#Entity
#Table(name = "external_object")
public class ExternalObject extends AbstractNameableEntity {
#OneToMany(cascade = CascadeType.ALL, mappedBy = "externalObject")
private List<ExternalMatch> matches;
// ...
}
resolved the repeated mapping issue yet leaving us with all the familiar troubles a biderectional mapping creates :)