Get eagerly collection of entities containing other eagerly got collections - jpa

I've got stuck on the M:N relation between entity and strings. An user can have more than one role and each role can be assigned to more than one user. Role is just a string. Roles are contained in table with two columns: roleId and roleName.
I've created two entities, but I'm absolutely unable to made it work. First entity is the user:
#Entity
#Table(name="appUsers")
public class UserEntity {
#Id
private String login;
private String password;
#OneToMany(fetch=FetchType.EAGER,mappedBy="user") //we always need to load user's roles
private Collection<UsersToRoles> roles;
#Transient
private Collection<String> roleNames;
public String getLogin() {
return login;
}
public String getPassword() {
return password;
}
#PostLoad
void prepareRoleNames() {
roleNames = new HashSet<String>(roles.size());
for (UsersToRoles mapping : roles)
roleNames.add(mapping.getNameOfRole());
}
public Collection<String> getRoles() {
return roleNames;
}
}
The second is entity associated with connecting table:
#Entity
#IdClass(UsersToRolesId.class)
public class UsersToRoles {
#Id
#SuppressWarnings("unused")
#Column(name="login")
private String login;
#Id
#SuppressWarnings("unused")
#Column(name="roleId")
private int roleId;
#ElementCollection(fetch=FetchType.EAGER)
#CollectionTable(name="userRoles", joinColumns={#JoinColumn(name="roleId")})
private List<String> roleName;
#ManyToOne
#JoinColumn(name="login")
#SuppressWarnings("unused")
private UserEntity user;
public String getNameOfRole() {
if (roleName.isEmpty())
throw new CommonError("Role name for roleId=" + roleId, AppErrors.ACCESSOR_UNAVAILABLE);
return roleName.get(0);
}
}
class UsersToRolesId {
private String login;
private int roleId;
/**
* Implicit constructor is not public. We have to
* declare public non-parametric constructor manually.
*/
public UsersToRolesId() {
}
#Override
public int hashCode() {
return 17*login.hashCode() + 37*roleId;
}
#Override
public boolean equals(Object obj) {
if (!(obj instanceof UsersToRolesId))
return false;
UsersToRolesId ref = (UsersToRolesId)obj;
return (this.login.equals(ref.login) && this.roleId == ref.roleId);
}
}
And the problem is, that the roleName collection is always null. I'm unable to get it work. When I make a mistake in table name in #CollectionTable annotation, it still works. The JPA does not fetch the subcollection at all. It makes select from table of user joined with table UsersToRoles, but the join to table userRoles is missing.
Can I ever do that? Can I get eagerly collection of entities containing another eagerly fetched collections?

Your mapping is completely wrong. UsersToRoles has a roleId column. Thus it refers to a single role. How could it have a collection of role names? The login column is mapped twice in the entity. Moreover, this looks like a simple join table to me, without any other attribute than the roleId and the login, which are foreign keys to the IDs of User and Role, respectively.
You should have two entities : User and Role, with a ManyToMany association using the UsersToRoles table as join table. That's it. The UsersToRoles table should not be mapped as an entity: it's a pure join table.

JPA providers usually have a configuration property denoting default eager fetch depth, i.e. hibernate.max_fetch_depth for Hibernate. Check if you can see more when you increase it.
Also, think about your design. Fetching subcollections of a collection eagerly might be a good idea only in limited scenarios (performance-wise). When you annotate your entity like that, you're going to use eager fetching in all use cases. Perhaps you'd be better off with "lazy" and fetching it eagerly only explicitly, with a query with a JOIN FETCH clause?

Related

How can I use JPA Query Methods to return an OR condition with NULL?

Am trying to create a Query that either matches all rows that equal tier or are NULL. Using Query Methods as described in Spring JPA Docs. The Default implementation below works if I just pass in the tier:-
#Entity
#Table(name = "tier")
class UuTier {
Long id;
Long tierId;
}
#Entity
#Table(name = "user")
class User {
#OneToOne
#JoinColumn(name="tier_id")
UuTier uuTier;
// Other Relationships
}
public interface UserRepository extends Repository<User, Long> {
List<User> findByTier_Id(#Param("tier")Long tier);
}
What I need is something like this, which is throwing an error " No property null found for type User". Can I achieve this ask using Query Methods?:-
public interface UserRepository extends Repository<User, Long> {
List<User> findByTierOrNull_Id(#Param("tier")String tier);
}
Following up from one of the responders (who for some reason deleted her post) - I got this to work!!
#Query("SELECT entity FROM User entity LEFT JOIN UuTier uuTier ON entity.uuTier.tier = uuTier.tier"
+ " WHERE entity.uuTier.tier = :tier OR entity.uuTier.tier IS NULL")
public List<User> findByTierOrNull_Id(#Param("tier") Long tier);

Creating an "IN" query with JPA 2.0 Criteria api

I am using tje JPA criteria API to create an "IN" query. I want to select Courses that are in certain Categories. The Categories are supposed to end up in the IN part of the query.
This is the Course entity. It has a reference to a Category entity, because each Course is in one Category.
#Entity
public class Course implements DomainObject {
private Long id;
private Integer version;
private String name;
private Category category;
#Override
#Id
#GeneratedValue
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
#ManyToOne
public Category getCategory() {
return category;
}
public void setCategory(Category category) {
this.category = category;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getVersion() {
return version;
}
public void setVersion(Integer version) {
this.version = version;
}
}
In my service I want to select Courses that are belong to certain (a list) of Categories.
public List<Course> findCourses(CourseFilter filter) {
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<Course> criteriaQuery = criteriaBuilder.createQuery(Course.class);
Root<Course> root = criteriaQuery.from(Course.class);
List<Predicate> predicateList = new ArrayList<Predicate>();
if (!filter.getCategories().isEmpty()) {
Predicate predicate = root.get(Course_.category).in(filter.getCategories());
predicateList.add(predicate);
}
Predicate[] predicates = new Predicate[predicateList.size()];
predicateList.toArray(predicates);
criteriaQuery.where(predicates);
TypedQuery<Course> typedQuery = entityManager.createQuery(criteriaQuery);
return typedQuery.getResultList();
}
When the query executes on the last line of the method it throws an error:
HTTP Status 500 - Request processing failed; nested exception is
org.springframework.dao.InvalidDataAccessApiUsageException:
org.hibernate.TransientObjectException: object references an unsaved transient instance
save the transient instance before flushing:nl.codebasesoftware.produx.domain.Category;
nested exception is java.lang.IllegalStateException:
org.hibernate.TransientObjectException: object references an unsaved transient instance
save the transient instance before flushing: nl.codebasesoftware.produx.domain.Category
I am not even sure I am using the right way to create an IN query. I think the criteria API is terribly complicated. But before I worry about the IN query I would like to know why Hibernate is throwing this TransientObjectException. The filter.getCategories() call results in actual categories, filled with a primary key id, etc.
Added:
Here is how I get the Category instance that I use to later fetch Courses with. This is also a DAO method that is called via a #Service from a #Controller method.
public Category findByName(String name) {
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<Category> query = builder.createQuery(Category.class);
Root<Category> root = query.from(Category.class);
Predicate predicate = builder.equal(root.get(Category_.urlTitle), name);
query.where(predicate);
TypedQuery<Category> typedQuery = entityManager.createQuery(query);
return getSingleResult(typedQuery);
}
So, Hibernate is telling me I am using Category objects that somehow reference an unsaved entity, but I don't see how. The Category that is returned from this method is just a Category that if fetched by Hibernate. I am not doing anything with it before I send it to the method that fetches Courses.
Here is my the controller method:
#RequestMapping(method = RequestMethod.GET, value = "/{categoryUrlName}")
public String setup(#PathVariable("categoryUrlName") String categoryUrlName, Model model){
// Fetch the category
Category category = categoryService.findByName(categoryUrlName);
// if no category found, throw a 404
if(category == null){
throw new ResourceNotFoundException();
}
// Fetch courses in this category
List<Course> courses = courseService.findCourses(category);
model.addAttribute("courses", courses);
model.addAttribute("category", category);
model.addAttribute("mainContent", "content/category");
return "main";
}
Before executing a query, Hibernate flushes the changes you made to persistent entities in the session. This ensures that the query will search on the latest state of all the entities. Unfortunately, one of the dirty entities that Hibernate tries to flush references a transient entity, and thus can't be flushed, which causes the exception. The exception doesn't come from the query itself, but from the flush before the execution of the query.
You probably did something like the following before executing the query:
Cat cat = em.find(Cat.class, catId); // cat is a persistent persistent entity
cat.setMate(new Mouse()); // the mouse has not been persisted, and cat references it.

OpenJPA auditing capabilities

I have a webservice that will be persisting and deleting data to a Database. I want to track in the database which usernames touched which rows of the database. In each table there are columns for usernames to be stored (update columns if you will). There are also triggers on the tables that will take a userID for the transaction and update that table with the username and password that attempted to insert. Is there a way in open JPA where I can get the username (which will be passed from the client) and update some kind of JPA object so that when JPA persists data, that user name will be thrown into the table?
One of the cleanest ways is to implement a common "mapped" superclass for your entities and use a method with #PrePersist annotation to populate the fields.
#MappedSuperclass
public class AuditedEntity {
#Id protected Integer id;
protected String lastUpdatedBy;
// Setters and getters here
#PreUpdate
#PrePersist
public void onChange() {
String user = .... // Do whatever is needed to get the current user
setLastUpdatedBy(user);
}
}
#Entity
public class Employee extends AuditedEntity {
// ....
}
Another option is to use a separate listener:
public interface AuditedEntity {
public static void setLastUpdatedBy(String username);
}
#Entity
#EntityListeners({ MyLogger.class, ... })
public class Employee implements AuditedEntity {
// ...
}
public class MyLogger {
#PreUpdate
#PrePersist
public void onChange(Object o) {
if(o instanceof AuditedEntity) {
String user = .... // Do whatever is needed to get the current user
((AuditedEntity) o).setLastUpdatedBy(user);
}
}
#PostPersist
#PostUpdate
public void logChange(Object o) {
// Log the successful operation
}
}

JPA2.0: Delete Entity in OneToMany RelationShip

How do I delete an entity in a OneToMany relationship.
#Entity
#NamedQueries({
#NamedQuery(name="User.findByUserNamePassword",
query="select c from User c where c.userName = :userName AND c.password = :password")
})
#Table(name="\"USER\"")
public class User implements Serializable {
#OneToMany(mappedBy="user", cascade=CascadeType.ALL, orphanRemove=true)
private List<Profession> professions;
public List<Profession> getProfessions() {
return professions;
}
public void setProfessions(List<Profession> professions) {
this.professions = professions;
}
public void addProfession(Profession profession){
if(this.professions == null){
this.professions = new ArrayList<Profession>();
}
this.professions.add(profession);
profession.setUser(this);
}
public void removeProfession(Profession profession){
if(this.professions != null){
professions.remove(profession);
profession.setUser(null);
}
}
}
Inside Profession Entity
#Entity
public class Profession implements Serializable {
#ManyToOne
#JoinColumn(name="UserId", nullable=false)
private User user;
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
Then inside my EJB I have this
#Stateless
#LocalBean
#TransactionAttribute(TransactionAttributeType.REQUIRED)
public class ScholarEJB{
/**
* Add a profession to a target user
* #param user
* #param profession
*/
public void addProfession(User user, Profession profession){
//Put the user in a managed state. It is important to do this before
//adding a new profession onto user
user = find(User.class, user.getId());
user.addProfession(profession);
this.create(user); //This is persist action
}
public void removeProfession(User user, Profession profession){
//Put the user in a managed state. It is important to do this before
//adding a new profession onto user
user = find(User.class, user.getId());
user.remove(user);
this.update(user); //merge action
//this.create(user) //also try this as well, but it does not work
}
}
Now addProfession work beautifully, but removeProfession does not work. Not sure why? Help please. Do I need to evict caches?
If professions are only part of this relationship, then you can guarantee that when a profession is removed from the User's set it will also be removed from the database by turning on orphanRemoval on the OneToMany side of the relationship.
#OneToMany(mappedBy="user", cascade=CascadeType.ALL, orphanRemoval=true)
private List<Profession> professions;
This is what the JPA 2.0 specification states
The JPA 2.0 specification states that
Associations that are specified as
OneToOne or OneToMany support use of
the orphanRemoval option. The
following behaviors apply when
orphanRemoval is in effect:
If an entity that is the target of the
relationship is removed from the
relationship (by setting the
relationship to null or removing the
entity from the relationship
collection), the remove operation will
be applied to the entity being
orphaned. The remove operation is
applied at the time of the flush
operation. The orphanRemoval
functionality is intended for entities
that are privately "owned" by their
parent entity. Portable applications
must otherwise not depend upon a
specific order of removal, and must
not reassign an entity that has been
orphaned to another relationship or
otherwise attempt to persist it. If
the entity being orphaned is a
detached, new,or removed entity, the
semantics of orphanRemoval do not
apply.
If the remove operation is applied to
a managed source entity, the remove
operation will be cascaded to the
relationship target in accordance with
the rules of section 3.2.3, (and hence
it is not necessary to specify
cascade=REMOVE for the
relationship)[20].
My guess as to what is occurring is that your User has a OneToMany relationship to Profession and you user object has the profession. When you delete the Profession the user still has the reference. Because the mapping is cascade persist, it re persists the Profession.
You need to ensure that you remove the profession from the user's professions before deleting it.
If you are using EclipseLink there is a property that may also help, but fixing your code to maintain your model correctly is the best solution. You could also remove the cascade persist.
"eclipselink.persistence-context.persist-on-commit"="false"
or,
"eclipselink.persistence-context.commit-without-persist-rules"="true"
I just added orphanRemoval = true in the OneToMany relationship and I resolved it.
Class SolicitudRetorno:
#OneToMany(mappedBy = "solicitud", cascade = CascadeType.ALL, orphanRemoval = true)
#LazyCollection(LazyCollectionOption.FALSE)
#NotAudited
private List<RetornoMenor> hijosRetorno;
Class RetornoMenor:
#OneToMany(mappedBy = "solicitud", cascade = CascadeType.ALL, orphanRemoval = true)
#LazyCollection(LazyCollectionOption.FALSE)
#NotAudited
private List<RetornoMenor> hijosRetorno;
You might try clearing the user field in profession:
public void removeProfession(Profession profession){
if(this.professions != null){
professions.remove(profession);
profession.setUser(null); // disassociate profession from user
}
}
To be on the safe side, I would also check that the passed in profession's current user equals this, just in case someone passes in a profession belonging to another user.
This is the solution to my original question, however, I do not know if this is the best
My EJB bean
#PersistenceContext(unitName="Bridgeye2-ejbPU")
private EntityManager em;
public <T> T create(T t) {
em.persist(t);
return t;
}
public <T> T find(Class<T> type, Object id) {
return em.find(type, id);
}
public <T> void delete(T t) {
t = em.merge(t);
em.remove(t);
}
public <T> void removeAndClearCaches(T t){
this.delete(t);
clearCaches();
}
public <T> T update(T t) {
return em.merge(t);
Now in my Managed Bean, I do this
/**
* Add a new profession
*/
public void addNewProfession(){
Profession profession = new Profession();
newProfessions.add(profession);
}
/**
* Remove the profession
* #param profession
*/
public void removeProfession(Profession profession){
//This will remove the `profession` of the list
//at the presentation layer
this.myProfessions.remove(profession);
//This will remove the `profession` of the list
//at the persistence layer
scholarEJB.removeAndClearCaches(profession);
}

JPA merge readonly fields

We have the simplest CRUD task with JPA 1.0 and JAX-WS.
Let's say we have an entity Person.
#Entity
public class Person
{
#Id
private String email;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(insertable = false, updatable = false)
private ReadOnly readOnly;
#Column
private String name;
#XmlElement
public String getEmail()
{
return email;
}
public void setEmail(String email)
{
this.email = email;
}
#XmlElement
public Long getReadOnlyValue()
{
return readOnly.getValue();
}
// more get and set methods
}
Here is scenario.
Client make Web Service request to create person. On the server side everything is straightforward.
And it does work as expected.
#Stateless
#WebService
public class PersonService
{
#PersistenceContext(name = "unit-name")
private EntityManager entityManager;
public Person create(Person person)
{
entityManager.persist(person);
return person;
}
}
Now client tries to update person and this is where, as for me, JPA shows its inconsistence.
public Person update(Person person)
{
Person existingPerson = entityManager.find(Person.class, person.getEmail());
// some logic with existingPerson
// ...
// At this point existingPerson.readOnly is not null and it can't be null
// due to the database.
// The field is not updatable.
// Person object has readOnly field equal to null as it was not passed
// via SOAP request.
// And now we do merge.
entityManager.merge(person);
// At this point existingPerson.getReadOnlyValue()
// will throw NullPointerException.
// And it throws during marshalling.
// It is because now existingPerson.readOnly == person.readOnly and thus null.
// But it won't affect database anyhow because of (updatable = false)
return existingPerson;
}
To avoid this problem I need to expose set for readOnly object and do something like this before merge.
Person existingPerson = entityManager.find(Person.class, person.getEmail());
person.setReadOnlyObject(existingPerson.getReadOnlyObject()); // Arghhh!
My questions:
Is it a feature or just
inconsistence?
How do you (or would
you) handle such situations? Please
don't advice me to use DTOs.
Is it a feature or just inconsistence?
I don't know but I'd say that this is the expected behavior with merge. Here is what is happening when calling merge on a entity:
the existing entity gets loaded in the persistence context (if not already there)
the state is copied from object to merge to the loaded entity
the changes made to the loaded entity are saved to the database upon flush
the loaded entity is returned
This works fine with simple case but doesn't if you receive a partially valued object (with some fields or association set to null) to merge: the null fields will be set to null in the database, this might not be what you want.
How do you (or would you) handle such situations? Please don't advice me to use DTOs.
In that case, you should use a "manual merge": load the existing entity using find and update yourself the fields you want to update by copying the new state and let JPA detect the changes and flush them to the database.