i have two entities Price<-1----1->PriceDetail mapped as OneToOne.
how can i handle different scenarios for this relation. so i have cases where i always want a new price and a new pricedetail,
but i also would be able to create a new price only and update the pricedetail (with data from a previous price-entity).
my current solution is to remove the pricedetail-entity, how can it be done with updating the pricedetail-entity?
#Entity
class Price {
#OneToOne(cascade=CascadeType.ALL,mappedBy = "price")
private PriceDetail priceDetail;
}
#Entity
class PriceDetail {
#OneToOne
private Price price;
}
save-method:
EntityManage em = getEntityManager();
for (Price price : getAllPrices()){
Price oldPrice = Price.getById(price.getId());
if (!oldPrice.equals(price)){ //if we have price-changes
if (PriceCatalog.entryExists(oldPrice)){ //if the current-price is in a catalog
//current solution: remove entry from PriceDetail, but i want to update PriceDetail-Entity, pointing
//to the newly created price
em.remove(oldPrice.getPriceDetail());
em.commitTransaction();
oldPrice.setActive(false); //referenced price in PriceCatalog is now inactive
//sets id null, so that a new price-entity is created
price.setId(null);
price.setActive(true);
em.persist(price); //also inserts a new price-detail
}else {
em.merge(price);
}
}
}
em.commitTransaction();
because of CascadeType.ALL-Annotation in Price-Entity, JPA tries to insert a new PriceDetail-Entity.
approach 1:
price.getPriceDetail().setId(oldPrice.getPriceDetail().getId());
-> Error: insert into pricedetail violates unique-constraint: Key already exists
approach 2:
//ommit cascade
#OneToOne(mappedBy = "price")
protected PriceDetail priceDetail;
then approach 1 works, but creating a complete new price results in:
During synchronization a new object was found through a relationship that was not marked cascade PERSIST
approach 2 is not an option in you case, this is the correct mapping to do a bidirectional one-to-one association:
//you must do this to handle the bidirectional association
#OneToOne(mappedBy = "price")
protected PriceDetail priceDetail;
Now the problem is :price is a new entity then the entityManager will call persit operation on price.getpriceDetail() because cascade persist is triggered automatically (not cascade-merge) to avoid this strange behaviour you can do the following.
EntityManage em = getEntityManager();
for (Price price : getAllPrices()){
Price oldPrice = Price.getById(price.getId());
if (!oldPrice.equals(price)){ //if we have price-changes
if (PriceCatalog.entryExists(oldPrice)){ //if the current-price is in a catalog
//current solution: remove entry from PriceDetail, but i want to update PriceDetail-Entity, pointing
//to the newly created price
//em.remove(oldPrice.getPriceDetail());
//em.commitTransaction();
oldPrice.setActive(false); //referenced price in PriceCatalog is now inactive
PriceDetail priceDetailold = price.getPriceDetail();
price.setPriceDetail(null);
priceDetailold.setPrice(null);
//sets id null, so that a new price-entity is created
price.setId(null);
price.setActive(true);
em.persist(price); //inserts a new price
price.setPriceDetail(priceDetailold);
em.merge(price);// attach the pricedetail to the price
}else {
em.merge(price);
}
}
}
em.commitTransaction();
Related
There are two entities,
1. Status (two columns: statusId status)
e.g. values: 1 ACTIVE, 2 INACTIVE, etc.)
2. Coupon (three columns: couponId title statusId)
e.g. values: 10 Oranges 1, 20 Apples 2, etc.)
Trying to figure out what relationship to be used and how. I tried OneToOne, OneToMany, ManyToOne, ManyToMany etc. but every time Status entity gets a new row added
e.g. 21 ACTIVE, 22 ACTIVE, 23 Active, etc.
The Coupon entity should get a new row every time a new coupon is saved and use the primary key of status in status id field but the status entity should be get a new row added.
Yes, Many coupons can share the same status.
The following code produces the following results
Before execution
Status Table
StatusId STATUS
1 ACTIVE
2 INACTIVE
Coupon Table
CouponId Title STATUS
<no rows at present>
After execution
Status Table
StatusId STATUS
1 ACTIVE
2 INACTIVE
3 ACTIVE (This row should not get inserted)
Coupon Table
CouponId Title StatusId
10 Apples 3 (StatusId should be 1 instead of 3)
JSON Request:
{
"title": "Apples",
"status": "ACTIVE",
}
Code:
#Autowired
CouponRespository couponRepository;
#RequestMapping(value = "/coupon", method = RequestMethod.POST)
void createCoupon(#RequestBody Coupon coupon) {
couponRepository.save(coupon);
}
public interface CouponRespository extends JpaRepository<Coupon, Long> {
}
#Entity
public class Status implements Serializable {
#Id
#SequenceGenerator(name = "STATUS_STATUSID_GENERATOR", sequenceName = "STATUS_ID_SEQ")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "STATUS_STATUSID_GENERATOR")
#Column(name = "STATUS_ID")
private long statusId;
#Column(name = "STATUS")
private String status;
}
#Entity
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "couponId")
#JsonIgnoreProperties({ "statusId" })
public class Coupon implements Serializable {
#Id
#SequenceGenerator(name = "COUPON_COUPONID_GENERATOR", sequenceName = "COUPON_ID_SEQ")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "COUPON_COUPONID_GENERATOR")
#Column(name = "COUPON_ID")
private long couponId;
#Column(name = "TITLE")
private String title;
#Column(name = "STATUS_ID")
private Long statusId;
// Need help here how to set this up properly
#ManyToOne(cascade = { CascadeType.ALL })
#JoinColumn(name = "STATUS_ID", insertable = false, updatable = false)
private Status status;
}
Any help is appreciated.
I have no idea how Jackson can possibly create a Coupon instance containing a Status instance from the JSON in your question, and even less how a new row could appear in the database, since the status field of COupon is not insertable, but anyway, suppose it does the equivalent of the following:
Coupon coupon = new Coupon();
coupon.setTitle("Apples");
Status status = new Status("ACTIVE");
coupon.setStatus(status);
You're asking to save that coupon to JPA. And you told JPA to cascade the persist operation to the status contained in the coupon. The status doesn't have any ID. So what can JPA do other than create a new one? How could it guess that what you actually want is to associate the new coupon with the already existing status identified by the ID 1? It can't. It does what you're telling it to do.
So, what should you do?
First, remove the statusId field from Coupon. This is the ID of the status, and you have the status in the coupon, so this information is redundant.
Second, you want the coupon to be associated to an existing status. So, get a reference to this existing status, and set it in the coupon before saving the coupon:
// this should execute a query that retrieves the Status entity
// that has the "ACTIVE" status
Status status = statusRepository.findStatusByStatus("ACTIVE");
coupon.setStatus(status);
couponRepository.save(coupon);
Third: since you don't want two status in database with the same status, add a unique constraint to the table.
Fourth: since you don't want to save or modify or delete the status when you save or modify or delete a coupon that references this status, remove cascade = { CascadeType.ALL }.
Fifth: since you want the changes to coupon.status to be persistent, remove insertable = false, updatable = false.
Sixth: since what your receive as JSON in your controller doesn't have the structure of a Coupon, and is not a Coupon instance but a title and a status text that allow you to find the Status with that text and create a Coupon instance, create a class CouponCommandDTO, which has the same structure as your JSON, and make that the type of the parameter of your controller. Then transform this DTO to an actual Coupon using the code shown above.
I have a User entity generated in Netbeans from an existing database table. The table has a column lastUpdatedByUser that is a User entity. Most of the tables in this database have a lastUpdatedByUser column and queries against those entities correctly return a user object as part of the result.
Ex. Retrieve FROM ProductionTable WHERE date = 'someDate' has a lastUpdatedByUser object that shows who last updated the table row and the rest of their user attributes.
If the productionTable data is edited in the web-app and submitted I need to update the lastUpdatedByUser column.
Users userUpdating = usersService.selectUserEntityByUserId(userId);
Users userEntity = usersFacade.findSingleWithNamedQuery("Users.findByUserId", parameters);
SELECT u FROM Users u WHERE u.userId = :userId
returns a User object that contains a lastUpdatedByUser that is a User object that contains a lastUpdatedByUser that is a User object that contains a lastUpdatedByUser object.... (I have no clue how many there are, and twenty rows of these adds up)
After I persist this
productionEntity.setLastUpdatedByUser(userUpdating);
I get Json StackOverflowError in the next request for the updated entity
gson.toJson(updatedProductionEntity)
The Users entity definition:
#OneToMany(mappedBy = "lastUpdatedByUser")
private Collection<Users> usersCollection;
#JoinColumn(name = "LastUpdatedByUser", referencedColumnName = "UserId")
#ManyToOne
private Users lastUpdatedByUser;
#OneToMany(mappedBy = "lastUpdatedByUser")
private Collection<Production> productionCollection;
How can edit that such that I continue to get a user object as part of other entities like Production, but only a single lastUpdatedByUser object for a User entity?
Thanks for any insight.
I'm guessing this is my issue:
#JoinColumn(name = "LastUpdatedByUser", referencedColumnName = "UserId")
as I found a FK in the Users table to its own UserId
Love refactoring
================================
Drop that FK from the Users table and regenerate the entity in Netbeans and I get
private Integer lastUpdatedByUser;
like it should be
instead of
private Users lastUpdatedByUser;
Now I get to edit all the entities that have valid FKs into the Users table and code and...
Thanks for listening.
my
#Entity
#Table(name = "Creditcard")
#AdditionalCriteria( ..... )
public class Customer implements Serializable {
#Id
#Column(name ="CustomerId")
private long customerId;
#Column(name = "cardNumber");
private String cardNumber;
#Column(name = "apply_date")
private java.sql.Date date;
}
Example Table Data for CustomerID 1234:
CustomerId|cardNumber|apply_date|....other fields
----------|----------|----------|----------------
0000000123|0000000001|2013-01-01|----------------
0000000123|0000000002|2013-09-10|----------------
Yes, I know, the Primary Key has to be a Composite Key (EmbeddedID), but I still have to figure it out.
Due to the #AdditionalCriteria I only get 1 entry (because the other card is "banned")
but I need to get the 'apply_date' from cardNumber '1'.
Is something like that possible?
Like:
#Column(name = "apply_date")
#GetMinValue(field_name = "CustomerId")
private java.sql.Date date;
Thanks in advance!
First, your entity should represent a row in the database, not all rows. So your entity probably should be a "CreditCard" entity, using "cardNumber" as the primary key, or what ever uniquely identifies the database row.
Then, since CustomerId seems to be a foreign key probably to a table that has customer data, you would have a Customer Entity that has a 1:M relationship to CreditCards. This customer entity could then have a transient date attribute that you set in a JPA postLoad event, getting the value from a JPQL query : "select cc.date from CreditCard cc where cc.customerId = :customerId";
Setting up an Customer entity that only uses a single card/row from a CreditCard table seems like a bad idea, as what will you do when the customer gets another creditCard assigned - it is the same customer, but a new card. If you use separate entity objects, you just keep the same Customer entity and assign a new creditcard object.
I have a REST interface for a datamodel that has several one-to-many and many-to-many relationships between entities. While many-to-many relationships seem easy to manage statelessly, I'm having trouble with one-to-many. Consider the following one-to-many relationship:
Employee:
#ManyToOne
#JoinColumn(name = "Company_id")
private Company company;
Company:
#OneToMany(mappedBy = "company", cascade = CascadeType.ALL, orphanRemoval=true)
public Set<Employee> employees = new HashSet<Employee>();
When a company is updated, its employee collection may have been updated as well (employees removed or added) but since the REST interface only allows updating the company as a whole, I cannot explicitly delete or add employees.
Simply replacing the collection does not work, but I found that this seems to work:
public void setEmployees(Set<Employee> employee) {
this.employees.clear(); // magic happens here?
this.employees.addAll(employees);
for (Iterator<Employee> iterator = employees.iterator(); iterator.hasNext();) {
Employee employee = (Employee) iterator.next();
employee.setCompany(this);
}
}
Is this the way it should be done, or is there a better way?
EDIT: In fact the above does not work! It appears to work at first, but then it will break with:
Exception in thread "main" java.lang.IllegalStateException: An entity copy was already assigned to a different entity.
I assume this happens because the db already contains a set of employees and if any of the "old" employees are also part of the replacement set, they collide with the ones in the database.
So what is the right way to replace the set?
First make sure equals is implemented properly. As per hibernate spec: http://docs.jboss.org/hibernate/orm/4.1/manual/en-US/html/ch04.html#persistent-classes-equalshashcode
I had a similar problem doing a merge. Essentially I had to fetch the existing employees associated with the company. I had to merge any changes to existing employees, and then add any new employees.
Query query = em.createQuery("select e from Employee e where e.company = '" + company.getId() + "'");
Collection<Employee> existingEmployees = new LinkedList<Employee>();
try{
Iterables.addAll(existingEmployees, (Collection<Employee>) query.getResultList());
}
catch(NoResultException nre){
//No results
}
for(Employee existingEmployee : existingEmployees){
for(Employee employee : company.getEmployees()){
if(existingEmployee.name().equals(employee.name())){
employee.setId(existingEmployee.getId());
}
employee.setCompany(company);
}
}
i think you have no better choice then to replace the existing collection and simply set the new one provided by the REST response.
I have a problem, I have a wizard that can update data AND insert data. So, if I have an existing list of team members, I can update their roles but If necessary, I can also add/insert a person to this team. So I can update roles and insert a new team member into the same table all during the same transaction. Data can be updated to and inserted to table teamMembers.
When I try to add a new teamMember, I also have an existing member where I simply want to update his role.
Both changes happen to the same table called TeamMember. When I debug the context, everything looks good. it shows that there are two changes that will occur for the TeamMember table. One transaction is the update and the other transaction is the insert. When I perform an update using:
var teamMember = new TeamMember
{
Name = user.FullName,
UserProfileId = user.UserProfileId,
RoleId = user.RoleId
};
TeamMemberList.Add(teamMember);
project.TeamMembers = TeamMemberList;
//And then call
this.Context.Projects.Attach(project);
this.Context.Entry(project).State = System.Data.EntityState.Modified;
it updates but the record that needs to be inserted fails.
HOW CAN I DO BOTH AN INSERT AND UPDATE TO THE SAME TABLE DURING THE SAME TRANSACTION?
CURRENT ERROR IS:
The changes to the database were committed successfully, but an error occurred while updating the object context. The ObjectContext might be in an inconsistent state. Inner exception message: A referential integrity constraint violation occurred: The property values that define the referential constraints are not consistent between principal and dependent objects in the relationship.
I think you need to add the TeamMember entity to the context's global list. Something like:
var teamMember = new TeamMember()
{
Name = user.FullName,
UserProfileId = user.UserProfileId,
RoleId = user.RoleId
}
project.TeamMembers.Add( teamMember );
this.Context.TeamMembers.Add( teamMember );
this.Context.SaveChanges();
How about loading the existing project entity first and then adding members.
var project = this.Context.Project.Where(p => p.ID = "bar").Include("TeamMembers").FirstOrDefault();
var teamMember= new TeamMember
{
Name = user.FullName,
UserProfileId = user.UserProfileId,
RoleId = user.RoleId
};
project.TeamMembers.Add(teamMember);
this.Context.SaveChanges()