Thanks for taking a look at this question in advance.
Basically, I have a few entities and here's the relationship.
Table A ---< Table AB >---- Table B
And the entity classes:
class A {
#ManyToMany
#JoinTable(name = "A_B", joinColumns = { #JoinColumn(name = "A_ID",nullable = false) }, inverseJoinColumns = { #JoinColumn(name = "B_ID", nullable = false) })
private List<B> bList;
}
class B {
#ManyToMany(mappedBy = "bList")
private List<A> aList;
}
Now I have 2 sessions accessing the same A object. I changed the bList value of A and saved it in session 1. So now, the object A in session 2 is stale and I saved an object C, which has reference of object A in its stale state.
I wonder why WITHOUT using cascade = {PERSIST, MERGE} by putting it into the #ManyToMany annotations, saving object C would also update the stale state of A back into the database, so the changes I made about object A in session 1 is gone?
Related
I am writing an application that will query a massive Database, that cannot be changed.
For that reason, my application does not need to map all Objects, since that would be useless and time consuming.
All entities there mapped are #Immutable.
I came across this relationship:
I want to Map Order, and have it reference Customer. It is, in fact, a Many to One Relationship, it just happens two be two Join clauses away.
I am not interested in neither R nor B, since they convey no information related to my requirement.
I envision something like this, but I know the syntax is invalid:
#Entity
#Immutable
#Table(name = "Order")
public class Order implements Serializable {
#Id
#Column(name = "id")
private Long id;
#ManyToOne
#JoinColumns(value =
#JoinColumn(table = "R", name = "id", referencedColumnName = "R_id"),
#JoinColumn(table = "Customer", name = "id", referencedColumnName = "Customer_id")
)
private Customer customer;
... more data and getters/setters omitted ...
}
#Entity
#Immutable
#Table(name = "Customer")
public class Customer implements Serializable {
#Id
#Column(name = "id")
private Long id;
... more data and getters/setters omitted ...
}
Is there a way I can do this, without creating an entity for R?
EDIT: -------------------------
I tried the following, as per suggestion:
#ManyToOne
#JoinTable(name = "R",
joinColumns = #JoinColumn(name = "id", referencedColumnName = "R_id"),
inverseJoinColumns = #JoinColumn(name = "id", referencedColumnName = "Customer_id"))
private Customer customer;
However, I get the following error:
Unable to find column with logical name: R_id in org.hibernate.mapping.Table(Order) and its related supertables and secondary tables
You could use the #JoinTable annotation for the following schema.
in this way
#Entity
#Table(name = "Order")
public class Order {
// ...
#ManyToOne
#JoinTable(
name = "R",
joinColumns = #JoinColumn(name = "ord_id"),
inverseJoinColumns = #JoinColumn(name = "customer_id"))
private Customer customer;
// ...
}
But for your case it looks like not possible to avoid usage of entity for intermediate table R due to the lack of foreign key to the Order table in the R.
I already created question before which was complicated. So I am creating a new one which will be as simple as possible.
I am having a problem with Spring JPA especially twice nested structure.
Here is a super simple relationship diagram:
The country has multiple rivers and rivers can flow through multiple countries. The river has multiple bridges but the bridge can be built over 1 river only. So M:N -> 1:N
This is the expected result:
{
countries: [
{
countryID: 1,
rivers: [
{
riverID: 1,
bridges: [{
bridgeID: 1
}]
}
]
}
]
}
And this is what I have.
Country entity
public class Country {
#Id
#GeneratedValue(strategy= GenerationType.IDENTITY)
private Long id;
... more columns
#ManyToMany(fetch = FetchType.EAGER,
cascade = {
CascadeType.PERSIST,
CascadeType.MERGE
})
#JoinTable(name = "country_river",
joinColumns = { #JoinColumn(name = "countryid") },
inverseJoinColumns = { #JoinColumn(name = "riverid") })
private Set<River> rivers = new HashSet<>();
River entity
public class River {
#Id
#GeneratedValue(strategy= GenerationType.IDENTITY)
private Long id;
... more columns
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.REMOVE)
#JoinColumn(name = "riverid")
private Set<Bridge> Bridges;
Don't need to mention Bridge entity since there is only ID column specified ( I am using unidirectional relation)
The problem is, that bridges are not filtered based on country. As you can see in the expected output, I would like to list all countries and it's associated rivers and bridges on them in the given country.
Do I have to create a custom query on Bridges inside River entity (If so, how.)? Or do I have to somehow change the structure of the database?
I have the following entities:
TEAM
#Entity
#Table
public class Team {
[..]
private Set<UserTeamRole> userTeamRoles;
/**
* #return the userTeamRoles
*/
#OneToMany(cascade = { CascadeType.ALL }, mappedBy = "team", fetch = FetchType.LAZY)
public Set<UserTeamRole> getUserTeamRoles() {
return userTeamRoles;
}
/**
* #param userTeamRoles
* the userTeamRoles to set
*/
public void setUserTeamRoles(Set<UserTeamRole> userTeamRoles) {
this.userTeamRoles = userTeamRoles;
}
}
and
USER_TEAM_ROLE
#Entity
#Table(name = "user_team_role")
public class UserTeamRole {
#ManyToOne(cascade = CascadeType.MERGE, fetch = FetchType.LAZY)
#JoinColumn(name = "FK_TeamId")
public Team getTeam() {
return team;
}
}
Now, when updating a Team entity that contains for example Team.userTeamRoles = {UTR1, UTR2} with {UTR1, UTR3}, I want UTR2 to be deleted. But the way I do it now, the old list remains the same and it only adds UTR3 to the list.
This is how I do it at the moment:
if (!usersDualListData.getTarget().isEmpty()) {
// the role for each user within the team will be "employee"
team.setUserTeamRoles(new HashSet<UserTeamRole>());
Role roleForUser = roleService
.getRoleByName(RoleNames.ROLE_EMPLOYEE.name());
for (User user : usersDualListData.getTarget()) {
UserTeamRole utr = new UserTeamRole();
utr.setUser(user);
utr.setTeam(team);
utr.setRole(roleForUser);
team.getUserTeamRoles().add(utr);
}
}
teamService.updateTeam(team);
I thought that by doing team.setUserTeamRoles(new HashSet<UserTeamRole>()); the list would be reset and because of the cascades the previous list would be deleted.
Any help is appreciated. Thank you
Instead of replacing the collection (team.setUserTeamRoles(new HashSet<UserTeamRole>());) you have to clear() the existing one. This happens because if Hibernate loads the entity (and its collections) from DB, it "manages" them, ie. tracks their changes. Generally when using Hibernate it's better not to create any setters for collections (lists, sets). Create only the getter, and clear the collection returned by it, ie:
team.getUserTeamRoles().clear();
Another thing is that you miss orphan deletion (ie. delete child object when it's removed from collection in the parent). To enable it, you need to add #OneToMany(orphanRemoval=true) in owning entity.
Hi all the below solution works in that it creates a record in the MeetingRoomRequest table and also adds the associated amenities to that request into the MeetingRoomRequestAmenityLink table. However it just feels a bit clunky, so I was wondering if there is a nicer solution out there (i.e. not having to create 2 context instances) using MVC 3 and Entity Framework??
Please note i've set up the necessary relationships (one to many) in SQL Server and Entity Framework.
Also please note AmenityList is an array of id's (e.g. [1,2,4])
private readonly IDataRepository<MeetingRoomRequest> _meetingRoomRequestRepository = new DataRepository<MeetingRoomRequest>();
private readonly IDataRepository<MeetingRoomRequestAmenityLink> _meetingRoomRequestAmenityLinkRepository = new DataRepository<MeetingRoomRequestAmenityLink>();
var meetingRoomRequestToAdd = new MeetingRoomRequest
{
User = meetingRoomRequestViewModel.User,
UserEmail = meetingRoomRequestViewModel.UserEmail,
Title = meetingRoomRequestViewModel.Title,
Comments = meetingRoomRequestViewModel.Comments,
StartDateTime = meetingRoomRequestViewModel.StartTime,
EndDateTime = meetingRoomRequestViewModel.EndTime,
RequestStatusID = (int)Enums.RequestStatus.New,
AttendeeCount = meetingRoomRequestViewModel.AttendeeCount,
AttendeeType = meetingRoomRequestViewModel.AttendeeType,
OfficeID = meetingRoomRequestViewModel.OfficeId,
LocationID = meetingRoomRequestViewModel.LocationId,
};
_meetingRoomRequestRepository.Add(meetingRoomRequestToAdd);
_meetingRoomRequestRepository.SaveChanges();
var meetingRoomRequestAdded = meetingRoomRequestToAdd;
foreach (var item in meetingRoomRequestViewModel.AmenityList)
{
var meetingRoomRequestAmenityLinkToAdd = new MeetingRoomRequestAmenityLink
{
AmenityID = item,
MeetingRoomRequestID = meetingRoomRequestAdded.MeetingRoomRequestID
};
_meetingRoomRequestAmenityLinkRepository.Add(meetingRoomRequestAmenityLinkToAdd);
_meetingRoomRequestAmenityLinkRepository.SaveChanges();
}
The way you are going about it looks right, but there are some improvements that could be made in efficiency of processing the request.
Since these are a child/parent relationship, you can create the parent entity and then attached the childern in the foreach loop before you call save changes on the parent entity. EF will automatically populate the foreign key value on the child object with the primary (or associated key) from the parent.
You can continue to use your Entity without having to save it back out to a variable. EF's object tracking will continue to track this throughout your function.
By moving the savechanges outside of the foreach loop, you are reducing the number of calls. I believe the same amount of SQL will be sent on the one final call, but you may see increases of not having the connection open/close. There may be other built in efficiencies as well from EF
The Code
var meetingRoomRequestToAdd = new MeetingRoomRequest
{
User = meetingRoomRequestViewModel.User,
UserEmail = meetingRoomRequestViewModel.UserEmail,
Title = meetingRoomRequestViewModel.Title,
Comments = meetingRoomRequestViewModel.Comments,
StartDateTime = meetingRoomRequestViewModel.StartTime,
EndDateTime = meetingRoomRequestViewModel.EndTime,
RequestStatusID = (int)Enums.RequestStatus.New,
AttendeeCount = meetingRoomRequestViewModel.AttendeeCount,
AttendeeType = meetingRoomRequestViewModel.AttendeeType,
OfficeID = meetingRoomRequestViewModel.OfficeId,
LocationID = meetingRoomRequestViewModel.LocationId,
};
_meetingRoomRequestRepository.Add(meetingRoomRequestToAdd);
foreach (var item in meetingRoomRequestViewModel.AmenityList)
{
meetingRoomRequestToAdd.MeetingRoomRequestAmenityLinks.Add(new MeetingRoomRequestAmenityLink
{
AmenityID = item
});
}
_meetingRoomRequestRepository.SaveChanges();
So, I have an unidirectional one-to-many relationship where I want to keep the children in an ordered list. Since they already have an "index" property, I tried to follow the advice on http://code.google.com/appengine/docs/java/datastore/jdo/relationships.html and use the "list-ordering" extension to use that index-property to determine the order of the children instead of using an auto-generated one.
Unfortunately, as soon as I add the annotation it stops returning children and only gives me an empty list.
I recreated the problem with this simple example:
#PersistenceCapable(detachable = "true")
#FetchGroup(name = "parent.children", members = {#Persistent(name = "children")})
public class Parent {
#PrimaryKey
#Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
private Key key;
#Persistent
#Order(extensions = #Extension(vendorName="datanucleus", key="list-ordering", value="index ASC"))
private List<Child> children;
// getters/setters
}
#PersistenceCapable(detachable = "true")
public class Child {
#PrimaryKey
#Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
private Key key;
#Persistent
private Integer index;
// getters/setters
}
DAO:
public void save(T entity) {
PersistenceManager pm = getPersistenceManager();
Transaction tx = pm.currentTransaction();
try {
tx.begin();
pm.makePersistent(entity);
tx.commit();
} finally {
if(tx.isActive())
tx.rollback();
pm.close();
}
}
public T get(Key key, String... fetchGroups) {
PersistenceManager pm = getPersistenceManager();
Transaction tx = pm.currentTransaction();
addFetchGroups(pm, fetchGroups);
try {
tx.begin();
pm.setDetachAllOnCommit(true);
T entity = (T) pm.getObjectById(entityClass, key);
tx.commit();
return entity;
} finally {
if(tx.isActive())
tx.rollback();
pm.close();
}
}
Test code:
Parent parent = new Parent();
Child child = new Child();
child.setIndex(10);
parent.getChildren().add(child);
mParentDao.save(parent);
Parent parent2 = mParentDao.get(parent.getKey(), "parent.children");
Is there anything in particular that I am doing wrong?
[EDIT] Here is the related log output:
Datastore: Putting entity of kind PARENT with key PARENT(no-id-yet)
Datastore: Putting entity of kind CHILD with key PARENT(3)/CHILD(no-id-yet)
Datastore: INDEX : 10
Datastore: Committed datastore transaction: 0
Datastore: Started new datastore transaction: 1
Datastore: Getting entity of kind PARENT with key PARENT(3)
Datastore.Retrieve: Preparing to query for all children of PARENT(3) of kind CHILD
Datastore.Retrieve: Added sort: index ASCENDING
Datastore.Retrieve: Query had 0 results.
Datastore: Committed datastore transaction: 1
Im using GAE plugin 1.7.0 with JDO and my scenario is exactly the same. I have a list of items and i need to maintain their order as well.
Now i used the app for a long period of time without doing the above changes (without implementing the feature).
Today i implemented order feature using the article and the data isn't being retrieved! the data is present in database but are not fetched during parent object load. Even though the list is marked with:
#Persistent(defaultFetchGroup = "true")
#Element(dependent = "true")