I got a model set up as follows:
#Table(name = "parent")
#Entity
public class Parent extends IdEntity {
#ElementCollection(fetch = EAGER)
#CollectionTable(
name = "foo",
joinColumns = #JoinColumn(name = "parent_id")
)
private List<Foo> foos = new ArrayList<>();
}
#Embeddable
public class Foo {
#NotNull
#OneToOne(optional = false, cascade = ALL)
private Bar bar;
}
#Entity
public class Bar extends IdEntity {
private String baz; // marked as unique in the DB
}
On every update of a Parent instance, I get an exception saying there is a duplicate Bar→baz entry.
org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint [bar.baz]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement
I clean the foos collection manually and re-addAll the Foo entries on every update.
My understanding is that due to the nature of ElementCollection, it cascades all the operations and removes the orphans. Since I have a cascade = ALL on Foo→bar then that should mean that all those entries would get removed and readded as well. What might be causing the "duplicate entry" error then?
Related
I dont know if this is a bug or there is something I am missing, but there is a problem when you try to use Inheritance.JOINED when the superclass has a composite primary key.
I have the following classes:
(Superclass)
#Entity
#Table(name = "tipos_opciones_misiones")
#IdClass(TipoOpcionMisionId.class)
#Inheritance(strategy = InheritanceType.JOINED)
#DiscriminatorColumn(name = "clase", discriminatorType = DiscriminatorType.STRING)
#ReadOnly
public abstract class TipoOpcionMision{
#Id
#ManyToOne
#JoinColumn(name="tipo_mision")
private TipoMision tipoMision;
#Id
#Column(name = "numero")
private int numero;
}
And a child class:
#Entity
#Table(name="tipos_misiones_opciones_comercio_compra")
#DiscriminatorValue("COMERCIO_COMPRA")
public class TipoOpcionMisionComercioCompra extends TipoOpcionMision{
#Column(name="valor")
double valor;
}
When I try to get a list on objects "TipoOpcionMision" the generated SQL ignores that there is a composite key [tipo_mision, numero] and it just uses "t1.numero = t0.numero". There should also be a "t1.tipo_mision= t0.tipo_mision".
SELECT **list of fields***, FROM tipos_misiones_opciones t0, tipos_misiones_opciones_comercio_compra t1 WHERE ((t0.tipo_mision = 'MISION_1') AND ((t1.numero = t0.numero) AND (t0.clase = 'COMERCIO_COMPRA')))
There is no errors, but I get false results because I am getting the values of the first row in the cartesian product.
I have tried to add:
#PrimaryKeyJoinColumns({
#PrimaryKeyJoinColumn(name = "tipo_mision", referencedColumnName = "tipo_mision"),
#PrimaryKeyJoinColumn(name = "numero", referencedColumnName = "numero")
})
But program fails when starting with the following error:
Exception Description: A #PrimaryKeyJoinColumns was found on the annotated element [class TipoOpcionMisionComercioCompra]. When the entity uses a single primary key, only a single (or zero) #PrimaryKeyJoinColumn should be specified.
It seems that for some reason Eclipselink is ignoring tthat the superclass has a composite primary key.
I have this query
DELETE
FROM bookings as b
WHERE b.check_out = CURRENT_DATE;
and I get
Cannot delete or update a parent row: a foreign key constraint fails (online_booking_app.booked_rooms, CONSTRAINT FK3x1lpikb2vk75nx41lxhdicvn FOREIGN KEY (booking_id) REFERENCES bookings (id))
My Booking entity has CascadeType.ALL and mapped by matches the other side - from my research these are some of the mistakes that could lead to this message.
Here is the BookingEntity:
#Entity
#Table(name = "bookings")
public class BookingEntity extends BaseEntity {
#OneToMany(mappedBy = "booking",cascade = CascadeType.ALL, orphanRemoval = true)
private List<BookedRoomsEntity> bookedRooms = new ArrayList<>();
private String firstName;
private String lastName;
public List<BookedRoomsEntity> getBookedRooms() {
return bookedRooms;
}
public BookingEntity setBookedRooms(List<BookedRoomsEntity> bookedRooms) {
this.bookedRooms = bookedRooms;
return this;
}
BookedRoomsEntity
#Entity
#Table(name = "booked_rooms")
public class BookedRoomsEntity extends BaseEntity {
#ManyToOne()
private BookingEntity booking;
public BookingEntity getBooking() {
return booking;
}
public BookedRoomsEntity setBooking(BookingEntity booking) {
this.booking = booking;
return this;
}
The CascadeType does only apply to EntityManager operations.
You therefore have two options:
Load the entities to be deleted first and then use EntityManager.remove
Remove the referencing entities first with a separate JPQL statement.
See the following relations:
The Table RECIPE_USERCATEGORY_REL has an ON UPDATE CASCADE trigger, so if I would execute the following command in psql console, the value of ruc_ucat_category will also be updated automatically.
update usercategory set ucat_category = 'OldCategory' where ucat_category = 'NewCategory';
This works.
The problem is now Hibernate. I have this method in my service class:
public void renameCategory(String userId, String fromCategory, String toCategory)
{
TypedQuery<UserCategory> query = entityManager.createNamedQuery("UserCategory.findAllCaseSensitiveByUserIdAndPrefix", UserCategory.class);
query.setParameter(ApplicationConstants.PARAM_USER_ID, userId);
query.setParameter("category", fromCategory);
List<UserCategory> resultList = query.getResultList();
if (resultList == null || resultList.isEmpty())
{
return;
}
UserCategory userCategory = resultList.get(0);
userCategory.setCategory(toCategory);
}
I can assure that userCategory has the value 'OldCategory'. In my opinion, the update should work, because the trigger of the database should update the value of the relation table, but nothing happens. Why is this so?
Additional information: In my Entities, there is no #OneToMany and #ManyToOne declaration on the USERCATEGORY <-> RECIPE_USERCATEGORY_REL relationship (only on RECIPE <-> RECIPE_USERCATEGORY_REL relationship it is). This is because RECIPE_USERCATEGORY_REL is not a real join table. USERCATEGORY is similar to a growing lookup table, so Hibernate must not interfere the workflow here. The only relation of USERCATEGORY <-> RECIPE_USERCATEGORY_REL is the referential integrity in the database.
This is what the entity looks like, but as I said, there is no hibernate relation to the category table since Hibernate should not take care about this relation:
#Table(name = "RECIPE_USERCATEGORY_REL")
public class RecipeUserCategoryRel implements Serializable
{
private static final long serialVersionUID = 1L;
#EmbeddedId
private RecipeUserCategoryRelPk recipeUserCategoryRelPk = new RecipeUserCategoryRelPk();
#MapsId("rcpId")
#ManyToOne
#JoinColumn(name = "ruc_rcp_id", referencedColumnName = "rcp_id", insertable = false, updatable = false)
private Recipe recipe;
...
}
and...
#Embeddable
public class RecipeUserCategoryRelPk implements Serializable
{
private static final long serialVersionUID = 1L;
#Column(name = "ruc_rcp_id")
private Long rcpId;
#Column(name = "ruc_ucat_category")
private String category;
#Column(name = "ruc_ucat_acc_identifier")
private Long identifier;
public RecipeUserCategoryRelPk()
{
}
...
//getters, setters, hashcode, equals
}
I read in some other postings that it is not allowed in JPA to change the primary key. However, my use case is definitely changing the primary key, but in my case, it's not a common use case and no 'real' part of the application, but there are cases where users need to modify old data, so I need to provide this functionality.
(As workaround, I made a native update query)
i have the following entity relationship:
SideA:
#Entity
#Table(name = "SideA")
public class SideA {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#CascadeOnDelete
#OneToMany(fetch = FetchType.LAZY, mappedBy = "sideA", cascade=CascadeType.ALL)
private List<ABAssociation> association = new ArrayList<ABAssociation>();
}
Side B:
#Entity
#Table(name = "SideB")
public class SideB {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#CascadeOnDelete
#OneToMany(fetch = FetchType.LAZY, mappedBy = "sideB", cascade=CascadeType.ALL)
private List<ABAssociation> association = new ArrayList<ABAssociation>();
}
ABAssociation:
#Entity
#Table(name = "ABAssociation")
public class ABAssociation {
#EmbeddedId
private ABAssociationPK pk = new ABAssociationPK();
#ManyToOne(cascade=CascadeType.MERGE)
#MapsId("aId")
private SideA sideA;
#ManyToOne(cascade=CascadeType.MERGE)
#MapsId("bId")
private SideB sideB;
}
ABAssociationPK:
#Embeddable
public class ABAssociationPK implements java.io.Serializable{
private long aId;
private long bId;
}
my problem is when i delete one side, the database delete the row in ABAssociation , but still stay in cache.
test code is like the follow:
SideA a = new SideA();
SideB b = new SideB();
entitymanager.persist(a);
entitymanager.persist(b);
ABAssociation ab = new ABAssociation()
ab.setSideA(a);
ab.setSideB(b);
entitymanager.persist(ab);
a.getABAssociationList().add(ab);
b.getABAssociationList().add(ab);
a = entitymanager.merge(a);
b = entitymanager.merge(b);
entitymanager.delete(a);
Since "a" was deleted, the relationship between "a" and "b" should be deleted too.
but when i check the "b.getABAssociationList().size()" it still there, even there is no rows in ABAssociation table in DB.
it this related to the share cache issue ?
In JPA you must maintain you object's relationships.
If you remove an object, you must first remove all references to it.
See,
http://en.wikibooks.org/wiki/Java_Persistence/Relationships#Object_corruption.2C_one_side_of_the_relationship_is_not_updated_after_updating_the_other_side
somewhat, yes. You need to remove B's reference to ABAssociation when ABAssociation gets deleted to maintain the cache with what is in the database. You might do this by using a preremove event on ABAssociation if you cannot do it in your application.
you can force update your list using evict() like this:
getEntityManager().getEntityManagerFactory().getCache().evict(ABAssociation.class);
I have an entity class that contains a map of key-value pairs which live in a different table and there may be no such pairs for a given entity. The relevant code for the entity classes is below.
Now, when I insert such an entity with persist(), then add key-value pairs, and then save it with merge(), I get duplicate entry errors for the related table that stores the key-value pairs. I tried to hold back insertion until the keys were added, to have one call to persist() only. This led to duplicate entry errors containing an empty (zero) id in the foreign key column (ixSource).
I followed the process in the debugger, and found that eclipselink seems to be confused about the cascading. While it is updating the entity, it executes calls that update the related table. Nonetheless, it also adds those operations to a queue that is processed afterwards, which is when the duplicate entry errors occur. I have tried CascadeType.ALL and MERGE, with no difference.
I'm using static weaving, if it matters.
Here's the entities`code, shortened for brevity:
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
#DiscriminatorColumn(name = "sType")
#Table(name = "BaseEntity")
public abstract class BaseEntity extends AbstractModel
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ix")
private long _ix;
}
#Entity
#Table(name = "Source")
public class Source extends BaseEntity
{
#OneToMany(cascade = CascadeType.MERGE)
#JoinTable(name = "SourceProperty", joinColumns = { #JoinColumn(name = "ixSource") })
#MapKey(name = "sKey")
private Map<String, SourceProperty> _mpKeys;
// ... there's more columns that probably don't matter ...
}
#Entity
#Table(name = "SourceProperty")
#IdClass(SourcePropertyKey.class)
public class SourceProperty
{
#Id
#Column(name = "sKey", nullable = false)
public String sKey;
#Id
#Column(name = "ixSource", nullable = false)
public long ixSource;
#Column(name = "sValue", nullable = true)
public String sValue;
}
public class SourcePropertyKey implements Serializable
{
private final static long serialVersionUID = 1L;
public String sKey;
public long ixSource;
#Override
public boolean equals(Object obj)
{
if (obj instanceof SourcePropertyKey) {
return this.sKey.equals(((SourcePropertyKey) obj).sKey)
&& this.ixSource == ((SourcePropertyKey) obj).ixSource;
} else {
return false;
}
}
}
I can't see how those errors would occur. Could you include the SQL and ful exception.
What version of EclipseLink are you using, did you try the latest release?
Why are you calling merge? Are you detaching the objects through serialization, if it is the same object, you do not need to call merge.
It could be an issue with the #MapKey, does it work if you remove this?