I am writing small app, using Play Framework 2.0 which uses Ebean as ORM.
So I need many-to-many relationship between User class and UserGroup class.
Here is some code:
#Entity
public class User extends Domain {
#Id
public Long id;
public String name;
#ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
public Set<UserGroup> groups = new HashSet();
}
#Entity
public class UserGroup extends Domain {
#Id
public Long id;
public String name;
#ManyToMany(mappedBy="groups")
public Set<User> users = new HashSet();
}
Database scheme generator generates good scheme for that code with intermediate table and all work quite ok, till I using many-to-many.
So I am adding group in one request:
user.groups.add(UserGroup.find.byId(groupId));
user.update();
And trying output them to System.out in another:
System.out.println(user.groups);
And this returns:
BeanSet deferred
Quick search show that BeanSet is lazy-loading container from Ebean. But seems like it doesn't work in proper way or I missed something important.
So is there any ideas about what I am doing wrong?
You need to save associations manually
user.groups.add(UserGroup.find.byId(groupId));
user.saveManyToManyAssociations("groups");
user.update();
Related
I'm trying to implement entity auditing in my Java Spring Boot project using spring-data-envers. All the entities are being created as they should, but I've come up against a brick wall when executing the query.
parentRepository.findRevisions(id).stream().map(Parent::getEntity).collect(Collectors.toList());
During this select the repository is supposed to fetch info also from the child entity, instead I get unable to find <child object> with {id}.
According to my experiments categoryId is being searched in the Category_Aud table, instead of the actual table with desired data.
Code snippets:
#Data
#Entity
#Audited
#NoArgsConstructor
public class Parent {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Enumerated(EnumType.STRING)
private Status status;
#Enumerated(EnumType.STRING)
private Type requestType;
private String fullName;
#ManyToOne
#JoinColumn(name = "child_id")
private Child child;
}
#Data
#Entity
#Audited
#NoArgsConstructor
public class Child {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String description;
}
I've extended Parent with RevisionRepository
#Repository
public interface ParentRepository extends RevisionRepository<Parent, Long, Long>, JpaRepository<Parent, Long>
And annotated my SpringBootApplication entry class with:
#EnableJpaRepositories(repositoryFactoryBeanClass = EnversRevisionRepositoryFactoryBean.class)
I couldn't find any explanation for this so far, how can make parentRepository get what I need?
The underlying problem here is that the reference from a versioned entity isn't really properly defined. Which variant of the reference should be returned? The one at the start of the version you use as a basis, the one at the end? The one that exists right now?
There are scenarios for which each variant makes sense.
Therefor you have to query the revisions yourself and can't simply navigate to them.
I have a Project and Employee entities, which has ManyToMany relationship like below.
#Entity
public class Project {
#Id #GeneratedValue
private int projectId;
private String projectName;
// has some additional columns
#ManyToMany(mappedBy = "projects")
private List<Employee> emp = new ArrayList<Employee> ();
....
.....
}
#Entity
public class Employee {
#Id #GeneratedValue
private int id;
private String firstName;
private String lastName;
#ManyToMany(cascade=CascadeType.ALL)
List<Project> projects = new ArrayList<Project> ();
....
....
}
When I use above entities, JPA create a mpping table 'Employee_Project' like below.
create table Employee_Project (emp_id integer not null, projects_projectId integer not null)
My question is, whenever new employee is added, I want to update both employee table and Employee_Project mapping table only, assume I know project id that I would like to map this employee to. (without touching project table/entity, I mean why should I provide complete project object, while saving employee entity alone, how can I do this via jpa?)
You don't need to provide the entire Project object. Use EntityManager.getReference(projectId) or JpaRepository.getOne(projectId).
Those methods will create a proxy object with the appropriate id, rather than loading the entire Project entity from the data store.
EDIT Your service method should look pretty much like the following:
#Transactional
public void createEmployee(Employee employee, Long projectId) {
employee.setProjects(List.of(projectRepository.getOne(projectId));
employeeRepository.save(employee);
}
As a side note, CascadeType.ALL (in particular, because it includes CascadeType.MERGE and CascadeType.REMOVE) doesn't make sense for #ManyToMany. Unless you're planning to create a Project by creating an Employee, CascadeType.PERSIST makes no sense, either.
I have two entities, AP and APStatus, in a one-to-one unidirectional relationship. Only the AP needs to be able to access the APStatus. The only thing the APStatus needs to know is the AP's id, which will also serve as the primary key for the APStatus. Essentially, APStatus is like an embedded object, but I want a separate table for it. Here's what I have:
My entities
#Entity
public class AP {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name="ID", nullable=false)
private int id;
#OneToOne
private APStatus apStatus;
//Getters and setters
}
#Entity
public class APStatus {
#Id
#Column(name="AP_ID", nullable=false)
private int apId;
//Getters and setters
}
My test
public class APRepositoryTest {
#Autowired
APRepository repository;
#Autowired
APStatusRepository statusRepository;
#Test
public void test() {
AP ap = new AP();
APStatus status = new APStatus();
statusRepository.save(status);
ap.setApStatus(status);
repository.save(ap);
status.setApId(ap.getId());
AP dbAp = repository.findOne(ap.getId());
assertNotNull(dbAp);
assertNotNull(dbAp.getApStatus());
assertEquals(dbAp.getId(), dbAp.getApStatus().getApId());
}
}
The assertEquals fails, expected: <1> but was <0>. And I already know why, I'm setting the status's apId field after I already saved the status and ap. But the problem with setting it before I save is that I would be setting the field equal to zero because after repository.save(ap) is executed, ap's id is auto generated to a new value (in this case 1). I've already experimented with making the relationship bidirectional and adding cascading effects but so far I've been unsuccessful. Can someone point me in the right direction or tell me how I can fix this? Thanks in advance.
EDIT: For now I'm going to make the relationship bidirectional and have the getter method in the APStatus class for the apId attribute look like the following. If someone has a better answer please share.
public int getApId() {
return ap.getId();
}
First, you cannot change an entities ID. It is set once and only once to identify the entity. After that, you are forcing the instance to reference something completely different, which is not supported in JPA.
That means that if the status.setApId call changes the apId field, it can only be set with the actual value.
Second, I don't quite understand the AP's apStatus reference. Does your AP have a foreign key to APStatus, or are you intending it to use the AP.ID APStatus.AP_ID relationship? My guess is that it is intended to reuse the AP.ID APStatus.AP_ID relationship as this seems more common, but only affects how you map it.
You can get this to work without changing your mappings by first saving the AP, using its ID in ApStatus and saving, then updating the relationship:
AP ap = new AP();
APStatus status = new APStatus();
repository.save(ap);
status.setApId(ap.getId());
statusRepository.save(status);
ap.setApStatus(status);
repository.save(ap);
Or you can change your mappings using JPA 2.0's derrived identities, set the cascade settings, and then only save either the the AP or the APStatus:
#Entity
public class AP {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name="ID", nullable=false)
private int id;
#OneToOne(mappedBy = "ap", cascade=CascadeType.ALL, orphanRemoval = true)
private APStatus apStatus;
//Getters and setters
}
#Entity
public class APStatus {
#Id
#OneToOne
#JoinColumn(name="AP_ID")
private AP ap;
//Getters and setters
}
Then you can simply use:
AP ap = new AP();
APStatus status = new APStatus();
status.setAp(ap);
ap.setApStatus(status);
repository.save(ap);
I'm seeing some weird behavior with my web service. I am using jersey for JAXRS and eclipselink for JPA. I suspect the issue is somewhere with JPA. To keep it short, I have 2 entities: History and Challenge.
#XmlRootElement
#Entity
public class History {
#Column
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
public long id;
#JoinColumn
#ManyToOne
public Challenge challenge;
public History() {
}
}
#XmlRootElement
#Entity
public class Challenge {
#Column
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
public long id;
#Column(length = 255)
public String title;
#Column(length = 1000)
public String description;
public Challenge() {
}
}
All entries for History and Challenge are present in the database and don't change. Usually, when I query all histories, I get back all the entries, and each History has a Challenge attached. However, sometimes, one or 2 of the history entries have null for the Challenge member (although the db is in perfect order). And if I reload the app inside Tomcat, the query works OK again. Later in time, this bug appears again. So I'm guessing it has something to do with how I use the EntityManager. Here is the code for querying all histories:
public List<History> getHistories() {
EntityManager em = emFactory.createEntityManager();
Query query = em.createQuery("SELECT h FROM History h");
List<History> list = (List<History>) query.getResultList();
em.close();
return list;
}
As I said earlier, the histories and challenges don't change. However, I have some other entities that refer to History and maybe I am doing something somewhere else that triggers this problem here. Should I call em.clear() or em.flush() in my method? Is it necessary in my case where I create an entity manager for each request?
EclipseLink enables a shared cache by default.
See,
http://wiki.eclipse.org/EclipseLink/FAQ/How_to_disable_the_shared_cache%3F
It seems you have corrupted you objects somehow. Perhaps you did not set the ManyToOne when you created the objects. Or somehow set it to null in your app?
Got GlassFish v3. I have an one-to-many entity. The problem is, that EclipseLink seems to ignore the fetch EAGER mode.
Here is my entities.
#Entity
public class Person implements Serializable
{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
#OneToMany(mappedBy = "person", fetch = FetchType.EAGER)
private List<Hobby> hobbies;
// getter and setter
}
A 1:n relationship
#Entity
public class Hobby
{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
#ManyToOne
#JoinColumn
private Person person;
// getter and setter
}
And the bean
#javax.ejb.Remote
public interface Testing
{
public void addTestData();
public List<Person> getTestData();
}
#javax.ejb.Stateless
public class TestingBean implements Testing
{
#javax.persistence.PersistenceContext
private EntityManager entityManager;
public void addTestData()
{
Person p = new Person();
p.setName("JOE");
entityManager.persist(p);
Hobby h1 = new Hobby();
h1.setName("h1");
h1.setPerson(p);
entityManager.persist(h1);
}
public List<Person> getTestData()
{
TypedQuery<Person> gridQuery = entityManager.createQuery("SELECT e FROM Person e", Person.class);
return gridQuery.getResultList();
}
}
EDIT Client:
InitialContext context = new InitialContext();
Testing test = (Testing)context.lookup("java:global/dst2_1/TestingBean");
test.addTestData();
for(Person p: test.getTestData()) {
System.out.println(p.getName());
for(Hobby b : p.getHobbys()) {
System.out.println(b.getName());
}
}
context.close();
Using MySQL - Storing the data works. But if I fetch the data only the person is returned - not hobbies. Coudld you tell me what is wrong in my code?
EDIT sorry have tried so many things ... The code shown as above produces:
Exception Description: An attempt was made to traverse a
relationship using indirection that had a null Session. This often
occurs when a n entity with an uninstantiated LAZY relationship is
serialized and that lazy relationship is traversed after
serialization. To avoid this issue, ins tantiate the LAZY
relationship prior to serialization.
But the Person is returned correctly. Why does it specify LAZY while I am using EAGER?
You code looks correct. I can't see any way that the EAGER could be ignored.
Are you sure you get the error with this attribute, not another one?
Also ensure you recompile and deployed your code correctly. You most like have an old version deployed.
Make the eager object Serializable