My major problem is to remove (+persist) an entity:
I have an entity Property which has several PropertyAttributes - managed as list, i.e.
#OneToMany(mappedBy = "property", cascade = { CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH })
#JoinFetch(value = JoinFetchType.INNER)
private List<PropertyAttribute> propertyAttribute;
in the PropertyAttribute I have as well a reference to the Property, i.e.
#Id
#JsonIgnore
#JoinColumn(name = "property_id")
#ManyToOne(cascade = { CascadeType.MERGE })
private Property property;
the main problem arise when when I want to remove some of the attributes, e.g.
getEntityManager().remove(attrEntity);
because this is not found in the entity. If I introduce some code like
if (!getEntityManager().contains(attrEntity)) {
attrEntity = getEntityManager().merge(attrEntity);
}
System.out.println(getEntityManager().contains(attrEntity)); //==> always false
I have no clue why the merge() has no effect on the overall. Any ideas what to check or what to change?
Plus: I tried the workaround to remove the PropertyAttribute from the Property itself and merge(myChangedProperty) it but the removed attribute is not persisted, i.e. still in the database
Related
I have one parent entity that has two child entities as attributes.
I want to select all elements from the parent entity that have EITHER a childOne with a given parameter as personal attribute OR childTwo with that same given parameter as personal attribute.
Here are my three classes simplified:
The Parent Object:
#Entity
public class ParentObject {
#Id
private int id;
private int fkChildOne;
private int fkChildTwo;
#ManyToOne
#JoinColumn(name = "fk_child_one_id", referencedColumnName =
"child_one_id")
private ChildOne childOne;
#ManyToOne
#JoinColumn(name = "fk_child_one_id", referencedColumnName =
"child_one_id")
private ChildTwo childTwo;
// getters and setters
}
The Child One Object:
#Entity
public class ChildOne {
#Id
private int childOneId;
private String nameChildOne;
#OneToMany
#JoinColumn(name = "fk_child_one_id")
private List<ParentObject> parents;
// getters and setters
}
The Child Two Object:
#Entity
public class ChildTwo {
#Id
private int childOneId;
private String nameChildTwo;
#OneToMany
#JoinColumn(name = "fk_child_two_id")
private List<ParentObject> parents;
// getters and setters
}
The Specs Class:
public static Specification<ParentObject> checkName(String name) {
return Specifications.where(
(root, query, builder) -> {
final Join<ParentObject, ChildOne> joinchildOne =
root.join("childOne");
final Join<ParentObject, ChildTwo > joinchildTwo =
root.join("childTwo");
return builder.or(
builder.equal(joinchildOne .get("nameChildOne"), name),
builder.equal(joinchildTwo .get("nameChildTwo"), name)
);
}
);
}
When this spec is called in my service, I get no results. However, if I comment out one of the two joins and the corresponding Predicate in my builder.or method, then I get some results but they obviously don't match what I'm looking for, which is to select every ParentObject that have either ChildOne with that parameter or ChildTwo with that paramater.
Any clue what's wrong with the code ?
Finally got the solution : to fetch all the corresponding results, I had to add the type of the join which would be left join, since I wanted to fetch all ParentObjects regardless of owning childOne or ChildTwo objects.
final Join<ParentObject, ChildOne> joinchildOne =
root.join("childOne", JoinType.LEFT);
final Join<ParentObject, ChildTwo > joinchildTwo =
root.join("childTwo", JoinType.LEFT);
Great, now you have to choose if you need to join or fetch.To optimize the query and the memory, you should establish the relations as Lazy (#ManyToMany (fetch = FetchType.LAZY)), so you will only bring the objects that you demand.
The main difference is that Join defines the crossing of tables in a variable and allows you to use it, to extract certain fields in the select clause, for example, on the other hand, fetch makes it feed all the objects of that property. On your example,
a select from parent with join of children (once the relation is set to lazy) would only bring initialized objects of type parent, however if you perform a fetch, it would bring the parent and child objects initialized.
Another modification I would make is to change the type of the identifier to non-primitive, so that it accepts null values, necessary for insertion using sequences
I'm a bit confused by the effect of Cascade.ALL.
I have two classes:
class Task {
#OneToOne(mappedBy = "task", cascade = CascadeType.ALL, orphanRemoval = true)
private Status status;
}
class Status {
#OneToOne
#JoinColumn(name = "task_id", updatable = false)
private Task task;
#Column(name="name")
private String name;
}
I thought saving status does not affect class Task since the cascade is applied to class Task, not Status, but it does modify the task object in my database.
So I'm wondering if
Task task = status.getTask();
task.setName(xxx);
taskDao.save(task);
has same effect as
taskDao.save(status.getTask.setName(xxx));
Is it different for
statusDao.save(task.getStatus) and statusDao.save(status) ?
Thanks.
I think your annotations confuse who owns the relationship. On the one hand, you are specifying the #JoinColumn(name = "task_id") making the Status entity the owner of the relationship, while on the other, you specify #OneToOne(mappedBy = "task"... on the Task entity, making it the owner of the relationship.
So status owns the relationship and when you save it, it goes over to task and finds that it also owns the relationship and has a CascadeType.ALL on it, so it applies the cascading on the save (PERSIST).
You should probably decide which entity should own the relationship and either remove the mappedBy or the #JoinColumn
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 two classes, Customer and CustomerProduct. I want the customerId(from customer) to be mapped in CustomerProduct. I used OneToMany mapping as follows.
Customer.java
#XmlTransient
#OneToMany(fetch = FetchType.EAGER, mappedBy = "customer")
public Set<CustomerProducts> getCustomerProducts() {
return customerProducts;
}
CustomerProduct.java
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "customer_id", nullable = false)
public Customer getCustomer() {
return customer;
}
I was able to save the datas, but for selecting a customer, I used named query which returned a Customer with CustomerProduct as well. I used restws. I used Response.ok(cust).build(); where cust had all the values in the server side.
When I get the response in the client side, I am getting the customers but not the CustomerProduct.
Did I miss something?
You need to remove the #XmlTransient annotation, it prevents the customerProducts property from serialization to a response.
See: http://docs.oracle.com/javaee/6/api/javax/xml/bind/annotation/XmlTransient.html
And to avoid infinity loop during serialization you either need add #XmlTransient to the customer property of the CustomerProduct entity or fetch this property lazily.
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?