Why #OneToMany does not discriminate sub-entities in collections - java

I have 2 collections of different sub-entities in owning entity:
#Entity
#Table(name = "car")
public class Car {
#Id
private Long id;
#OneToMany(mappedBy = "owner", cascade = CascadeType.ALL, orphanRemoval = true, targetEntity = Headlight.class)
private Collection<Headlight> headlights = new ArrayList<Headlight>();
#OneToMany(mappedBy = "owner", cascade = CascadeType.ALL, orphanRemoval = true, targetEntity = Wheel.class)
private Collection<Wheel> wheels = new ArrayList<Wheel>();
}
#Entity
#Table(name = "part")
#DiscriminatorColumn(name = "type", discriminatorType = DiscriminatorType.STRING, length = 16)
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public abstract class Part {
#Id
private Long id;
private String manufacturer;
}
#Entity
#DiscriminatorValue("HEADLIGHT")
public class Headlight extends Part {
private Integer power;
#ManyToOne
#JoinColumn(name = "owner_id", referencedColumnName = "id")
private Car owner;
}
#Entity
#DiscriminatorValue("WHEEL")
public class Wheel extends Part {
private Integer size;
#ManyToOne
#JoinColumn(name = "owner_id", referencedColumnName = "id")
private Car owner;
}
I expect these two collections to be filled by instances of corresponding subclasses (2 headlights and 4 wheels):
#Test
public void testCar() {
Car car = new Car();
car.setId(1l);
Headlight light1 = new Headlight();
light1.setId(1l);
light1.setManufacturer("Osram");
light1.setPower(12);
light1.setOwner(car);
Headlight light2 = new Headlight();
light2.setId(2l);
light2.setManufacturer("Osram");
light2.setPower(12);
light2.setOwner(car);
car.getHeadlights().add(light1);
car.getHeadlights().add(light2);
Wheel wheel1 = new Wheel();
wheel1.setId(3l);
wheel1.setManufacturer("Bridgestone");
wheel1.setSize(16);
wheel1.setOwner(car);
Wheel wheel2 = new Wheel();
wheel2.setId(4l);
wheel2.setManufacturer("Bridgestone");
wheel2.setSize(16);
wheel2.setOwner(car);
Wheel wheel3 = new Wheel();
wheel3.setId(5l);
wheel3.setManufacturer("Bridgestone");
wheel3.setSize(16);
wheel3.setOwner(car);
Wheel wheel4 = new Wheel();
wheel4.setId(6l);
wheel4.setManufacturer("Bridgestone");
wheel4.setSize(16);
wheel4.setOwner(car);
car.getWheels().add(wheel1);
car.getWheels().add(wheel2);
car.getWheels().add(wheel3);
car.getWheels().add(wheel4);
entityManager.persist(car);
entityManager.flush();
entityManager.clear();
Car restoredCar = entityManager.find(Car.class, 1l);
Assert.assertEquals(2, restoredCar.getHeadlights().size());
Assert.assertEquals(4, restoredCar.getWheels().size());
}
Instead, first collection contains 6 headlights (partially filled) and second collection contains incorrect data:
org.hibernate.WrongClassException: Object with id: 1 was not of the specified subclass: com.commerzbank.tr.nonotc.repository.Wheel (loaded object was of wrong class class com.commerzbank.tr.nonotc.repository.Headlight)
Output SQL:
SELECT headlights0_.owner_id AS owner6_3_1_,
headlights0_.id AS id2_7_1_,
headlights0_.id AS id2_7_0_,
headlights0_.manufacturer AS manufact3_7_0_,
headlights0_.owner_id AS owner6_7_0_,
headlights0_.power AS power4_7_0_
FROM part headlights0_
WHERE headlights0_.owner_id=?
I expected discriminator column to be included into WHERE clause as well:
AND headlights0_.type = 'HEADLIGHT'
,but it is not there.
I could only fix this issue using Hibernate #Where(clause = "type = 'WHEEL') annotation.
Why isn't Hibernate work correctly in this case? I expect it has all necessary metadata information to be able to issue correct SQL.

You might want to try
#DiscriminatorOptions(force = true) on your abstract class "Part".
See also: About the use of #ForceDiscriminator/#DiscriminatorOptions(force=true)

Related

Bidirectional #ManyToOne with join table creates duplicate key

I'm implementing a #ManyToOne bidirectional relationship with a join table using hibernate, but when I'm persisting some data, the hibernate claims that the record in relationship table is being inserted twice, violating the unique constraint, as the error message below shows:
ERROR: org.hibernate.engine.jdbc.spi.SqlExceptionHelper - ERROR: duplicate key value violates unique constraint "tillage_sample_pkey"
Detail: Key (id_tillage, id_sample)=(82, 110) already exists.
I have the following tables:
Tillage (id, some other data) (One Tillage can have many samples)
Sample (id, some other data) (One Sample can have one Tillage only)
tillage_sample (id_tillage, id_sample) PK (id_tillage, id_sample)
When I create a Tillage object, I fill with a Sample. In the Sample Object, I point with the Tillage object, creating a "double binding".
I guess that this "double bind" is causing the trouble, as Tillage/Sample relationship is persisted by hibernate when is saving the Tillage and repeating the step when it tries to persist the Tillage inside the Sample (which is the same tillage object).
Here Goes my code, to help you to understand my issue:
Tillage.java
#Entity
#Table(name = "tillage")
public class Tillage implements Serializable {
private static final long serialVersionUID = 3605331584324240290L;
#Id
#GeneratedValue(generator = "tillage_id_seq", strategy = GenerationType.SEQUENCE)
#SequenceGenerator(name = "tillage_id_seq", sequenceName = "tillage_id_seq", allocationSize = 1)
private Integer id;
#Column(name = "name")
private String name;
// Other simple attributes
#ManyToOne
#JoinColumn(name = "id_farm")
#JsonBackReference
private Farm farm;
// This relation is the problematic one
#OneToMany(cascade=CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name = "tillage_sample",
joinColumns = { #JoinColumn(name = "id_tillage") },
inverseJoinColumns = {#JoinColumn(name = "id_sample") })
private List<Sample> sampleList;
// Although similar, this one is doing OK
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name = "tillage_owner",
joinColumns = { #JoinColumn(name = "id_tillage") },
inverseJoinColumns = {#JoinColumn(name = "id_owner") })
private List<Owner> ownerList;
// getters & setters
}
Sample.java
#Entity
#Table(name = "sample")
public class Sample implements Serializable {
private static final long serialVersionUID = 7064809078222302493L;
#Id
#GeneratedValue(generator = "sample_id_seq", strategy = GenerationType.SEQUENCE)
#SequenceGenerator(name = "sample_id_seq", sequenceName = "sample_id_seq", allocationSize = 1)
private Integer id;
#Column(name = "name")
private String name;
// Other simple attributes
// This completes the relation Tillage-Sample
#ManyToOne(cascade=CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name = "tillage_sample",
joinColumns = { #JoinColumn(name = "id_sample") },
inverseJoinColumns = {#JoinColumn(name = "id_tillage") })
private Tillage tillage = new Tillage();
#OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
#JoinTable(name = "sample_sample_item",
joinColumns = { #JoinColumn(name = "id_sample") },
inverseJoinColumns = {#JoinColumn(name = "id_sample_item") })
private List<SampleItem> sampleItemList;
// Getters and Setters
}
SomeService.java
...
#Override
public Tillage toTillage(TillageDTO dto) {
Tillage tillage = new Tillage();
tillage.setName(dto.getNameTillage());
// Fill the samples of the tillage
for(ArrSample sample : dto.getSamples().getArrSample()){
Sample s = new Sample();
s.setName(sample.getName());
// Setting the tillage in the Sample object
s.setTillage(tillage);
// Fill the items of the sample
for(Array arr : sample.getAreas().getArray()){
SampleItem si = new SampleItem();
si.setProduction(Double.parseDouble(arr.getProduction()));
// Double binding between sample and sampleItem
si.setSample(s);
s.getSampleItemList().add(si);
}
// Adding a sample to Tillage
tillage.getSampleList().add(s);
}
return tillage;
}
public void save(TillageDTO dto){
Tillage t = this.toTillage(dto);
// The error occurs when we persist the data
// The entityManager is Autowired by Spring and works in other places
entityManager.persist(tillage);
}
That's not a bidirectional OneToMany. That's too separate unidirectional associations using the same join table.
In a bidirectional association, one side must be the inverse of the other side. For a OneToMany, the One side must be the inverse side:
#OneToMany(mappedBy = "tillage", cascade=CascadeType.ALL, fetch = FetchType.EAGER)
private List<Sample> sampleList;

Hibernate throws "Repeated column in mapping for entity" when using OrderColumn with two lists

I have a tricky problem to let hibernate order two list of the same entity. With some code it could be easier to understand what I wanna do.
#MappedSuperclass
public abstract class AbstractParent {
List<CommonObject> commonObjects;
public abstract List<CommonObject> getCommonObjects();
}
#Entity
#Table
public class Child1 extends AbstractParent {
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name="child1_id", nullable = false)
#OrderColumn(name = "sort_index")
public List<CommonObject> getCommonObject() {
return this.commonObjects;
}
}
#Entity
#Table
public class Child2 extends AbstractParent {
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name="child2_id", nullable = false)
#OrderColumn(name = "sort_index")
public List<CommonObject> getCommonObject() {
return this.commonObjects;
}
}
But because hibernate handle the mapping of the column "sort_index", it's mapped twice (for Child1 and Child2). So I get this error :
Caused by: org.hibernate.MappingException: Repeated column in mapping
for entity ... column: sort_index (should be mapped with
insert="false" update="false")
I know that I can resolve this problem if I put two different columns for sorting. But I would like to know if someone has a better solution to give me.
Thanks,
I added a test to replicate your issue on GitHub and it works after modifying the mappings to this:
#MappedSuperclass
public abstract class AbstractParent {
public abstract List<CommonObject> getCommonObjects();
}
#Entity(name = "Child1")
public class Child1 extends AbstractParent {
#Id
private Long id;
#OneToMany(targetEntity = CommonObject.class, cascade = CascadeType.ALL, orphanRemoval = true)
#JoinTable(name = "child_1_common_objects", joinColumns = #JoinColumn(name="child1_id", nullable = false))
#OrderColumn(name = "sort_index")
private List<CommonObject> commonObjects = new ArrayList<>();
public List<CommonObject> getCommonObjects() {
return this.commonObjects;
}
}
#Entity(name = "Child2")
public class Child2 extends AbstractParent {
#Id
private Long id;
#OneToMany(targetEntity = CommonObject.class, cascade = CascadeType.ALL, orphanRemoval = true)
#JoinTable(name = "child_2_common_objects", joinColumns = #JoinColumn(name="child2_id", nullable = false))
#OrderColumn(name = "sort_index")
private List<CommonObject> commonObjects = new ArrayList<>();
public List<CommonObject> getCommonObjects() {
return this.commonObjects;
}
}
#Entity(name = "CommonObject")
public class CommonObject {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
}

Hibernate callback for many-to-many join table operations?

Consider this scenario:
A Car can have a number of issues
An issue can be shared by a number of cars
I want to define this relationship using a Many-To-Many mapping with a join table.
And I want a generic column MODIFIED_TS to be maintained automatically on all three tables (yes - I can do it with database triggers - but that is not my ambition).
I have experimented with a variety of hibernate entity listeners and interceptors. They work fine - except for the join table.
Is there anyone out there who knows why callbacks like e.g. preInsert and onSave do not fire when rows are inserted or updated on the join table?
I am using:
Hibernate 4.3.10.Final
spring-data-jpa 1.8.1.RELEASE (does it matter?)
Base entity class:
#MappedSuperclass
public abstract class BaseEntity {
#Column(name = "MODIFIED_TS")
private Timestamp modifiedTs;
}
Car:
#Entity
#Table(name = "CAR")
public class Car extends BaseEntity {
#Id
#Column(name = "ID", nullable = false)
private long id;
#Column(name = "LICENSE_PLATE_NUMBER", nullable = false, length = 20)
private String licensePlateNumber;
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "CAR_ISSUE", inverseJoinColumns = {#JoinColumn(name = "CAR_ID")},
joinColumns = {#JoinColumn(name = "ISSUE_ID")})
private Collection<Issue> issues;
}
Issue:
#Entity
#Table(name = "ISSUE")
public class Issue extends BaseEntity {
#Id
#Column(name = "ID", nullable = false)
private long id;
#Column(name = "TEXT", nullable = false, length = 2000)
private String text;
}
and CarIssue:
#Entity
#Table(name = "CAR_ISSUE")
public class CarIssue extends BaseEntity {
#Id
#Column(name = "ISSUE_ID", nullable = false)
private long issueId;
#Id
#Column(name = "CAR_ID", nullable = false)
private long carId;
}
My listener:
public class MyEntityListener implements PreUpdateEventListener, PreInsertEventListener {
public boolean setAudit(Object entity, String[] properties, Object[] state) {
if (entity instanceof BaseEntity) {
BaseEntity baseEntity = (BaseEntity) entity;
baseEntity.setModifiedTs(new Timestamp(new Date().getTime()));
List<String> propertiesList = Arrays.asList(properties);
state[propertiesList.indexOf("modifiedTs")] = baseEntity.getModifiedTs();
}
return false;
}
#Override
public boolean onPreInsert(PreInsertEvent event) {
return setAudit(event.getEntity(), event.getPersister().getPropertyNames(), event.getState());
}
#Override
public boolean onPreUpdate(PreUpdateEvent event) {
return setAudit(event.getEntity(), event.getPersister().getPropertyNames(), event.getState());
}
}
and finally this piece of code:
Car car = new Car();
car.setLicensePlateNumber("ABC123");
ArrayList<Issue> issues = new ArrayList<>();
Issue issue = new Issue();
issue.setDescription("Two wheels missing");
issues.add(issue);
car.setIssues(issues);
carRepo.save(car);
This works fine - I get a row in all three tables - but MODIFIED_TS only gets populated for CAR and ISSUE.
Any help is greatly appreciated:-)

how to remove "null id ...entry (don't flush the Session after an exception occurs)"

helloĜŒI am web developer in php and recently migrateto javaEE.I create to table in mysql.
this is first class:
#Entity
#Table(name = "first")
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
#org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE, inclu`enter code here`de = "all")
public class first extends second {
#OneToOne(cascade = CascadeType.ALL)
private second A;
.
.
.
and this is my second class:
#Entity
#Table(name = "second", uniqueConstraints =
#UniqueConstraint(columnNames = {"feildone", "feildtwo"}))
public class second implements Serializable {
#OneToOne(cascade = CascadeType.ALL, mappedBy = "first")
public static final String FindOne = "findOne";
#Id
#GeneratedValue
Integer id;
private String feildtwo;
private String feildone;
#Temporal(javax.persistence.TemporalType.DATE)
private Date createTime;
#OneToMany(cascade = CascadeType.ALL)
public List<Progress> progress = new ArrayList<>();
private Integer num;
.
.
.
Try generating your ID automatically and see if u face the same problem
#Entity
#Table(name = "second", uniqueConstraints =
#UniqueConstraint(columnNames = {"feildone", "feildtwo"}))
public class second implements Serializable {
#OneToOne(cascade = CascadeType.ALL, mappedBy = "first")
public static final String FindOne = "findOne";
#Id
#GeneratedValue(strategy = GenerationType.AUTO) // try to use auto generate id guess this might help
Integer id;
private String feildtwo;
private String feildone;
#Temporal(javax.persistence.TemporalType.DATE)
private Date createTime;
#OneToMany(cascade = CascadeType.ALL)
public List<Progress> progress = new ArrayList<>();
private Integer num;

Hibernate create alias on many to many list

I have four class; UserGroup, UserAccount, Role, UserGroupRoleRelation and my db is IBM DB2
#Entity
#Table(name = "USER_GROUP")
public class UserGroup implements Serializable {
#Id
#Column(name = "USER_GROUP_ID")
#GeneratedValue
private Long id;
......
..
#OneToMany(mappedBy = "userGroup", cascade = CascadeType.ALL, orphanRemoval = true)
private List<UserGroupRoleRelation> userAccountsRole = new ArrayList<UserGroupRoleRelation>();
}
#Entity
#Table(name = "ROLE")
public class Role implements Serializable {
#Id
#Column(name = "ROLE_ID")
#GeneratedValue
private Long id;
......
#OneToMany(mappedBy = "role")
private List<UserGroupRoleRelation> userAccountInGroup = new ArrayList<UserGroupRoleRelation>();
}
#Entity
#Table(name = "USER_GROUP_ROLE_LINE", uniqueConstraints = #UniqueConstraint(columnNames = { "ROLE_ID", "USER_GROUP_ID" }))
public class UserGroupRoleRelation {
#Id
#GeneratedValue
#Column(name = "RELATION_ID")
private Long relationId;
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "USER_ACCOUNT_USER_GROUP_ROLE_LINE", joinColumns = { #JoinColumn(name = "RELATION_ID") }, inverseJoinColumns = { #JoinColumn(name = "USER_ID") }, uniqueConstraints = #UniqueConstraint(columnNames = { "USER_ID", "RELATION_ID" }))
private List<UserAccount> userAccountList = new ArrayList<UserAccount>();
#ManyToOne
#JoinColumn(name = "USER_GROUP_ID")
private UserGroup userGroup;
#ManyToOne
#JoinColumn(name = "ROLE_ID")
private Role role;
}
#Entity
#Table(name = "USER_ACCOUNT")
public class UserAccount implements Serializable {
#Id
#Column(name = "USER_ID")
#GeneratedValue
private Long id;
.....
#ManyToMany(mappedBy = "userAccountList", cascade = CascadeType.ALL)
private List<UserGroupRoleRelation> rolesInGroup = new ArrayList<UserGroupRoleRelation>();
}
I wanna find usergroups of a useraccount and i prepared a method with criteria. its like;
#Override
#Transactional
public List<UserGroup> findUserGroupOf(UserAccount userAccount) {
Criteria criteria = getSession().createCriteria(UserGroup.class);
criteria.createAlias("userAccountsRole", "userAccountsRole");
criteria.add(Restrictions.eq("userAccountsRole.userAccountList", userAccount));
return criteria.list();
}
But when i try to get result of that method, DB2 gives to me DB2 SQL Error: SQLCODE=-313, SQLSTATE=07004, SQLERRMC=null, DRIVER=3.63.75
Probably its about creating alias on many to many relation. I dont know what should i do to create alias on many to many. How can I get result of that function?
Thank
#Override
#Transactional
public List<UserGroup> findUserGroupOf(UserAccount userAccount) {
Criteria criteria = getSession().createCriteria(UserGroup.class);
criteria.createAlias("userAccountsRole", "userAccountsRole");
criteria.createAlias("userAccountsRole.userAccountList", "userAccountList");
criteria.add(Restrictions.eq("userAccountList.id", userAccount.getId()));
return criteria.list();
}
It works for me. I mean criteria on "id". But I don't understand why I cant check equality on object instead of id when there is ManyToMany list
It is not of creating alias. You are passing an object to hibernate on which it can not make any criteria. You need to create bidirectional mapping for that.Or else if you your requirement is just to fetch the the list of UserAccountList of particular UserGroup class you can follow the below code.
#Override
#Transactional
public List<UserGroup> findUserGroupOf(long userGroupId) {
Criteria criteria = getSession().createCriteria(UserGroup.class);
criteria.add(Restrictions.eq("id",userGroupId));
criteria.createAlias("userAccountsRole", "uar");
criteria.setFetchMode("uar.userAccountList",FetchMode.JOIN);
return criteria.list();
}

Categories