Reputation: 21
I have a Spring 3.2.1, Hibernate 4.1.9 and Spring Data application. I am using inheritance type joined for my entities and the hierarchy is as follows: a superclass DomainEntity and two classes SmartElement and LowLevelEntity that inherit from it, and that are inherited by other classes. A SmartElement has a Set of LowLevelEntities and the relationship is bidirectional.
DomainEntity class
@Entity
@Table(name = "entity")
@Inheritance(strategy = InheritanceType.JOINED)
public class DomainEntity implements Serializable
{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "entity_id", unique = true)
private long uuid;
@Column(name = "entity_name", unique = true)
protected String name;
@ManyToOne()
@JoinColumn(name = "location_id")
protected Location location;
@ManyToOne(cascade = {CascadeType.ALL})
@JoinColumn(name = "state_id")
protected State state;
// getters and setters
}
The SmartElment class
@Entity
@Table(name = "smart_element")
@Inheritance(strategy = InheritanceType.JOINED)
@PrimaryKeyJoinColumn(name = "smart_element_id")
public class SmartElement extends DomainEntity implements Serializable
{
@OneToMany (mappedBy = "connectedEntity",
cascade = {CascadeType.ALL}, orphanRemoval = true)
protected Set<LowLevelEntity> lowLevelEntityCollection;
// getters and setters
}
And the LowLevelEntity class
@Entity
@Table(name = "low_level_entity")
@Inheritance(strategy = InheritanceType.JOINED)
@PrimaryKeyJoinColumn(name = "low_level_entity_id")
public class LowLevelEntity extends DomainEntity implements Serializable
{
@ManyToOne()
@JoinColumn(name = "smart_element_id")
protected SmartElement connectedEntity;
public void setConnectedEntity(SmartElement connectedEntity)
{
if (connectedEntity != null)
{
this.connectedEntity = connectedEntity;
this.connectedEntity.addLowLevelEntity(this);
} else
{
if (this.connectedEntity != null)
{
this.connectedEntity.removeLowLevelEntity(this);
}
this.connectedEntity = connectedEntity;
}
// other fields and getters/setters
}
Now one of the classes that inherits from SmartElement is very simple, the Lighting class
@Entity
@Table(name = "lighting")
@PrimaryKeyJoinColumn(name = "lighting_id")
public class Lighting extends SmartElement implements Serializable
{
@Enumerated(EnumType.STRING)
protected LightingLevelType lightingLevel = LightingLevelType.NONE;
public LightingLevelType getLightingLevel()
{
return lightingLevel;
}
public void setLightingLevel(LightingLevelType lightingLevel)
{
this.lightingLevel = lightingLevel;
}
}
LightingLevel is an enum class
public enum LightingLevelType
{
NONE(0), QUARTER(0.25), HALF(0.5), THREE_QUARTERS(0.75), MAX(1);
private double value;
private LightingLevelType(double value)
{
this.value = value;
}
public double getValue(){
return value;
}
}
I am getting the error whenever I execute the following
SmartElement element = new Lighting("lamp", new SmartElementState(), kitchen);
//fields inherited from the superclass DomainEntity name, state and location
LowLevelEntity sensor = new LowLevelEntity("sensor", new SensorState(), kitchen);
sensor.setConnectedEntity(element);
and save the entities via cascade by persisting the containing location using a Spring Data JPARepository save method.
The error says:
could not get a field value by reflection getter of model.highlevel.smartelement.lighting.Lighting.lightingLevel;
If I execute the same code but change Lighting to SmartElement I do not have the error
SmartElement element = new SmartElement("lamp", new SmartElementState(), kitchen);
nor does it occur if I comment the line sensor.setConnectedEntity(element);
Sometimes there would be no errors with a different name of the Lighting instance which made me think that maybe there is a problem with the equals and hashCode method implementations. I tried different approaches for this, like here, or just using the name of the element without the id but I still got the error.
@Override
public boolean equals(final Object other)
{
if (this == other) return true;
if (! (other instanceof DomainEntity)) return false;
DomainEntity castOther = (DomainEntity) other;
return new EqualsBuilder()
.append(name, castOther.name).isEquals();
}
@Override
public int hashCode()
{
return new HashCodeBuilder()
.append(name).toHashCode();
}
I simply do not understand what is going on and what I am doing wrong. If someone has any idea or has come across such a case I would be very grateful for the input. I am sorry for the long post but I assume that it can be easier to understand when having the code as well.
Update
I have tried several things to try and find out where the error is coming from.
public int type
also has no effect, only removing it fixes the error but that is not an option.sensor.setConnectedEntity(element);
where the method only does this.connectedEntity = connectedEntity
. If I only add the object to the SmartElement set and do not set the reverse relationship there is no error but the connectedEntity is null. If I retrive both objects from the database, set the reverse relationship and save again the error does not appear.So I'm inclined to think that this has something to do with setting the connection in the owning part of the relation (the LowLevelEntity class is the owner if I'm not mistaken) involving entities that are not persisted yet and then saving the entities via cascade from the location. The strange thing is that I have just run this test with @Repeat(50) but only changed the name of the smartElement from 'lamp' to 'kitchen_lamp':
SmartElement element = new Lighting("kitchen_lamp", new SmartElementState(), kitchen);
LowLevelEntity lamp_kitchen = new LowLevelEntity("lamp_kitchen", new SensorState(), kitchen);
and no error. Also, in the console when the smart element has any other name than "kitchen_lamp" hibernate first inserts the LowLevelEntity and then tries to insert the smartElement, and with the name "kitchen_lamp" it inverses the insert which then has no error. Does anyone know why this would be happening and if there is something I can do about this?
Upvotes: 1
Views: 5113
Reputation: 7632
I once had a similar issue. You need to specify the targetEntity of your ManyToOne and vice versa.
public class LowLevelEntity extends DomainEntity implements Serializable
{
@ManyToOne(targetEntity=SmartElement.class)
@JoinColumn(name = "smart_element_id")
protected SmartElement connectedEntity;
}
and
public class SmartElement extends DomainEntity implements Serializable
{
@OneToMany (mappedBy = "connectedEntity", targetEntity=LowLevelEntity.class
cascade = {CascadeType.ALL}, orphanRemoval = true)
protected Set<LowLevelEntity> lowLevelEntityCollection;
// getters and setters
}
EDIT (after first comment):
I think I now remember I got that error with a wrong findBy method in a Spring Data Repository. Please check all your findBy Methods! I'm not sure how they work with enums, maybe that is the issue.
Upvotes: 0