I have three entities: Account, Partner, and Referral.
The Partner table is already full and has a link to the Account table.
When registering a user, the Account table is filled out.
Then I need to fill out the Referral table in which there are links to the Account and the Partner.
In this case, I need to check if there is a Referral link in the request, I need to check that it is in the Partner table and write to the ID Referral table of the Partner. And also take the ID from the table Account and also write it to the Referral table.
I have entity and controller
#Entity
#Table(name = "partner")
public class Partner implements Serializable {
#Id
#Column(name = "id", unique = true, nullable = false)
private Long id;
#OneToOne
#JoinColumn(name = "account_id")
private Account account;
#Column(name = "referral_link", nullable = false, unique = true)
#NotEmpty
private String referralLink;
Getter, Setter, and Constructor
#Entity
#Table(name = "referral")
public class Referral implements Serializable {
#Id
#Column(name = "id", unique = true, nullable = false)
private Long id;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "partner", cascade = CascadeType.ALL)
#JoinColumn(name = "partner_id")
private Set<Partner> partnerId = new HashSet<>();
#OneToOne
#JoinColumn(name = "account_id", nullable = false, unique = true)
private Account accountId;
#Column(name = "device_id")
private String deviceId;
#Column(name = "setup_date", nullable = false)
private Date setupDate;
Getter, Setter, and Constructor
In the Controller, I wrote this code:
Long defaultId = 6L;
if (referralLink == null) {
referral = new Referral(defaultId, account.getId();
referralService.create(referral);
} else {
List<Partner> partnerList = partnerService.getAll();
if (partnerList.contains(referralLink)) {
// How to get partnerId?
referral = new Referral(partnerId, account.getId();
} else {
referral = new Referral(defaultId, account.getId();
referralService.create(referral);
}
referralService.create(referral);
}
Many questions turned out:
How to get the element's id on the sheet that the referralLink belongs to?
How to add a default ID if the referralLink is empty?
When creating a Referral, he asks me for an entity Account instead of my Long and Set partners, what am I doing wrong?
You have many problems,
First in your Refferal class
#OneToMany(fetch = FetchType.LAZY, mappedBy = "partner", cascade = CascadeType.ALL)
#JoinColumn(name = "partner_id")
private Set<Partner> partnerId = new HashSet<>();
you cannot have JoinColumn at the two sides of the relationship, since this is a bidirectional relation you should have joincolumn in one side and mappedBy in the other side.
In your persistence logic you should have a reference of each side of the relation in the other side since the relation is bidirectional, I am talking about the Refferal and Partner.
How to get the element's id on the sheet that the referralLink belongs
to?
I am not sure what you mean by element's id but I suppose It is entity which has association with Partner, in this case you only need to find the Partner for a specific referralLink and use normal simple getters
How to add a default ID if the referralLink is empty?
simply check if the referralLink is empty and add an id, if you are talking about the autogeneratedId then why would u need to add such thing and even if you do it will be just ignored, if you are talking about id of another entity (foreign key) set the entity you need if the referralLink.
When creating a Referral, he asks me for an entity Account instead of
my Long and Set partners, what am I doing wrong?
In your referral entity you are specifying not null constraint on the account
#OneToOne
#JoinColumn(name = "account_id", nullable = false, unique = true)
private Account accountId;
so just remove "nullable = false"
Related
In my Spring boot app, there are two types of entities: User and Group:
User can own 0 to N groups
Group can have 1 to M members
In the User class there is a list of Group that he/she owns or is a member of, and in the Group class, there is a list of User (i.e. members).
These classes refer to each other using hibernate annotations.
class User {
#ManyToMany(cascade = CascadeType.REFRESH)
private List<Group> groups;
}
class Group {
#ManyToOne(cascade = CascadeType.REFRESH)
#NotNull
#JoinColumn(name="OWNER_ID", referencedColumnName="id")
private User owner;
#ManyToMany
#JoinTable(joinColumns = #JoinColumn(referencedColumnName = "id"), inverseJoinColumns = #JoinColumn(referencedColumnName = "id"))
private List<User> members;
}
In the User service layer there's a Delete method which is supposed to delete a user from the repository. This delete should fire a series of actions: all the groups owned by that user gets deleted, and the deleted groups should be removed from list of groups of their members. All these should be saved to the repository.
If I add other types of entities to this network, this process gets much more complicated.
My question is: Doesn't hibernate handle this automatically ? Should I grab each member and delete the group one by one and save it to the repository ?
CascadeType.REFRESH means Managed objects can be reloaded from the database by using the refresh method.
This will not help you solving your requirement. You need to use “orphanRemoval = true” CascadeType. “orphanRemoval = true” removes an owned object from the database when it’s removed from its owning relationship.
Example:
EmployeeEntity.java
#Entity #Table(name = "Employee")
public class EmployeeEntity implements Serializable
{
private static final long serialVersionUID = -1798070786993154676L;
#Id #Column(name = "ID", unique = true, nullable = false)
private Integer employeeId;
#Column(name = "FIRST_NAME", unique = false, nullable = false, length = 100)
private String firstName;
#Column(name = "LAST_NAME", unique = false, nullable = false, length = 100)
private String lastName;
#OneToMany(orphanRemoval = true, mappedBy = "employee")
private Set<AccountEntity> accounts;
}
AccountEntity.java
#Entity (name = "Account") #Table(name = "Account")
public class AccountEntity implements Serializable
{
private static final long serialVersionUID = 1L;
#Id #Column(name = "ID", unique = true, nullable = false)
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private Integer accountId;
#Column(name = "ACC_NO", unique = false, nullable = false, length = 100)
private String accountNumber;
#ManyToOne
private EmployeeEntity employee;
}
OR You can use CascadeType.ALL too.
For further reading, go through below link:
CascadeTypes
I'm creating a database entity object Order, and assign it to multiple entities of type BookingCode.
Problem: this creates a single order in db, which is fine. But the order itself has a #OneToOne OrderDescription, which occurs duplicate in the database.
#Entity
public class BookingCode {
#Id
private Long id;
#ManyToOne(cascade = {CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH, CascadeType.DETACH})
private Order order;
}
#Entity
public class Order {
#Id
private Long id;
private String orderName;
#OneToOne(mappedBy = "order", cascade = CascadeType.ALL, orphanRemoval = true)
private OrderDescription description;
}
#Entity
public class OrderDescription {
#Id
private Long id;
//for simplicity just one text element; of course multiple fields in real life
private String text;
#OneToOne
private Order order;
}
Test:
Order order = new Order();
order.setOrderName("test");
OrderDescription d = new OrderDescription("testdescr");
d.setOrder(order);
order.setDescription(d);
List<BookingCodes> codes = new ArrayList<>();
BookingCode code = new BookingCode();
code.setOrder(order);
codes.add(order);
BookingCode code2 = new BookingCode();
code2.setOrder(order); //using the same offer entity!
codes.add(order2);
codes = dao.save(codes); //CrudRepository from Spring
dao.findOne(codes.get(0).getId()); //this works, find an order which has one of the OrderDescriptions
Result:
In my database I then have two OrderDescription entries, where I would expect only one, because I reused the same Order object and assigned it to different BookingCode objects.
Like:
table order_descrption:
1;"de";"testdescr";"123456"
2;"de";"testdescr";"123456"
As Order has a #OneToOne relation to OrderDescription
And I even don't understand why the select using findOne() works correctly. Because in database I now have two OrderDescriptions that map to the same Order, but an Order can only have one of them.
Persist the order first and then assign it to both bookingCode .
I had a similar issue where I had an Order obj and its variable prevOrder was referring to itself i.e. Order entity. And when I stored order, it would end up storing duplicate records for prevOrder.
I had the following code:
#Entity
#Table(name = "orders")
public class Order implements Serializable {
#Id
#GeneratedValue(generator = "order_id_generator")
#SequenceGenerator(name = "order_id_generator", sequenceName = "order_id_sequence", allocationSize = 1)
#Column(name = "id", updatable = false, nullable = false)
private Long id;
#OneToOne(cascade = CascadeType.ALL, optional = true)
#JoinColumn(name = "previous_order_id", unique = true, updatable = false, referencedColumnName = "id")
private Order previousOrder;
#OneToOne(cascade = CascadeType.ALL, mappedBy = "previousOrder")
private Order nextOrder;
...
I tried various things including overriding equals and hashcode of Order, and adding a OneToOne mappedBy field 'nextOrder' etc. But noticed JPA didn't even call equals() to determine object's uniqueness. Ultimately I found out that JPA uses id field as the object's identifier and I wasn't storing the generated id while storing the object to a distrobuted cache. So it was all the time creating fresh objects during persistence.
I am having one hibernate pojo class which has 3 fields specified in #UniqueConstraint (unique together) where one of these 3 fields is nullable=true.
When I try to update entry with session.update(pojo) it updates all the entries in database which matches 2 fields (which are not nullable), so does hibernate avoid nullable fields while querying? or there is something what I should know about it?
Edit: Added class
#Entity
#Table (name = "details",
uniqueConstraints = {#UniqueConstraint(columnNames = {"service_id", "billing_item_id", "service_type_id"}, name="UK_name_it")}
)
public class Detail implements Serializable {
#ManyToOne
#JoinColumn(name = "service_id")
#ForeignKey(name = "FK_name2")
#Id
private Service service;
#ManyToOne
#JoinColumn(name="billing_item_id")
#ForeignKey(name = "FK_name3")
#Id
private BillingItem billingItem;
#ManyToOne
#JoinColumn(name="currency_id")
#ForeignKey(name = "FK_name4")
private Currency currency;
#ManyToOne
#JoinColumn(name="service_type_id")
#ForeignKey(name = "FK_name5")
private ServiceType serviceType;
#Column(name = "completed", nullable = false)
private boolean completed;
}
There doesn't seem to be any option like that to have a nullable field in composite key, so I had to end up by adding a integer autoincrement primary key to the table, and keeping service, billingItem and serviceType fields in #UniqueConstraint.
There is another option I could adopt, which is possible in certain scenarios, by adding a serviceType which is considered as All entry (basically when serviceType is null it applies to all the serviceTypes.) and instead of using null for serviceType point to this entry, this way we can have PK and no need to make serviceType a nullable field.
I have two classes.
public class Invoice {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "invoice_id", unique = true)
private int invId;
#OneToMany(mappedBy = "invoiceList", cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
private List<Item> itemList;
#Column(name = "invoice_amt", nullable = false)
private Double invAmt;
}
And,
public class Item {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "item_id", unique = true)
private int itemId;
#ManyToOne(optional = false, targetEntity = Invoice.class)
#JoinColumn(name="invoice_id")
private List<Invoice> invoiceList;
}
I am new to JPA. So my understanding may not be accurate.
My understanding is that, if I save Invoice, the invoice_id of that instant should cascade down to invoice_id of all the items.
However, I see Item being saved but get null in place of invoice_id of the Item.
What am I missing?
UPDATE!!! UPDATE!!!
Ok so I changed the #ManyToOne to be a singular attribute and did objItem.setInvoice(objInvoice) and saved it. However, I still get NULL on invoice_id.
You are annotating a many-to-one relation, but use collections on both sides. This will not work. The one-side has to map the relation to a singular attribute. In your case, it would be
#ManyToOne
private Invoice invoice
Perhaps you rather need a many-to-many relation. In this case, you will need to change the annotations to #ManyToMany and get rid of the cascades (they tend not to work as expected from a many-side).
targetEntity attribute and the #JoinColumn annotation are redundant on the invoice attribute of Item.
In order for the Item to save the id of the related invoice, you first need to set the invoice attribute of the Item since item is the owning side (the one where the relation information is stored).
I'm not sure this is your only problem, but a 1:n relationship shouldn't have a List both ways. If you turn List<Invoice> into a simple Invoice object, you'll at least be closer to a solution. We can go from there if your code still fails.
public class Item {
#ManyToOne(optional = false, targetEntity = Invoice.class)
#JoinColumn(name = "invoice_id")
private Invoice invoice;
}
public class Invoice {
#OneToMany(mappedBy = "invoiceList", cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
private List<Item> itemList;
}
QUESTIONS:
Does anyone know how to merge without having EntityManager trying to re-insert the foreign entity?
SCENARIO:
Just to set up a scenario that closely matches my case: I have two entities
#Entity
#Table(name = "login", catalog = "friends", uniqueConstraints =
#UniqueConstraint(columnNames = "username"))
public class Login implements java.io.Serializable{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "id", unique = true, nullable = false)
private Integer id;
#Column(name = "username", unique = true, nullable = false, length = 50)
private String username;
#Column(name = "password", nullable = false, length = 250)
private String password;
}
#Entity
#Table(name = "friendshiptype", catalog = "friends")
public class FriendshipType implements java.io.Serializable{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "id", unique = true, nullable = false)
private Integer id;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "username")
private Login login;
#Column(name = "type", unique = true, length = 32)
private String type;
...//other fields go here
}
Both the Login entity and the FriendshipType entity are persisted to the database separately. Then, later, I need to merge a Login row with a FriendshipType row. When I call entityManager.merge(friendship), it tries to insert a new Login which of course results in the following error
Internal Exception: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Duplicate entry 'myUserName1350319637687' for key 'username'
Error Code: 1062
Call: INSERT INTO friends.login (password, username) VALUES (?, ?)
My question, again, is how do I merge two objects without having enityManager trying to reinsert the foreign object?
Here is how I solve the problem. I finally figure the reason the merge is not resolving is because the login.id is auto generated by JPA. So since I really don't need an auto-generated id field, I remove it from the schema and use username as the #id field:
#Entity
#Table(name = "login", catalog = "friends", uniqueConstraints =
#UniqueConstraint(columnNames = "username"))
public class Login implements java.io.Serializable{
private static final long serialVersionUID = 1L;
#Id
#Column(name = "username", unique = true, nullable = false, length = 50)
private String username;
#Column(name = "password", nullable = false, length = 250)
private String password;
}
Another solution that occurred to me, which I didn't implement but may help someone else, should they need to have an auto-generated id field.
Instead of creating an instance of Login for the merger, get the instance from the database. What I mean is, instead of
Login login = new Login(); login.setUsername(username); login.setPassword(password);
Do rather
Login login = loginDao.getByUsername(username);
That way, a new id field is not generated making the entity seem different.
Thanks and up-votes to everyone for helping, especially to #mijer for being so patient.
You can make your #JoinColumn non updatable:
#JoinColumn(name = "login_id", updatable = false) // or
#JoinColumn(name = "username", referencedColumnName = "username", updatable= false)
Or try to refresh / fetch your Login entity again before merging the FriendshipType:
// either this
entityManager.refresh(friendship.getLogin());
// or this
final Login login = entityManager
.getReference(Login.class, friendship.getLogin().getId());
friendship.setLogin(login);
// and then
entityManager.merge(friendship);
But, as other suggested I belive that FriendshipType would be better represented by a #ManyToOne relationship or maybe by a Embeddable or ElementCollection
Update
Yet another option is to change the owning side:
public class Login implements java.io.Serializable {
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "friendshiptype_id")
private FriendshipType friendshipType;
// Other stuff
}
public class FriendshipType implements java.io.Serializable {
#OneToOne(fetch=FetchType.LAZY, mappedBy="friendshipType")
private Login login;
// Other stuff
}
This will affect your data model (login table will have a friendshiptype_id column instead of the other way around), but will prevent the errors that you are getting, since relationships are always maintained by the owning side.
Have you tried cascade=MERGE? I.e.
#OneToOne(fetch = FetchType.LAZY, cascade=CascadeType.MERGE)
#JoinColumn(name = "username")
private Login login;
UPDATE
Another possible option is to use #ManyToOne (it's save as the association is unique)
#ManyToOne(fetch = FetchType.LAZY, cascade=CascadeType.MERGE)
#JoinColumn(name = "username")
private Login login;
You can do it with your original #Id setup. i.e.
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "id", unique = true, nullable = false)
private Integer id;
You can, but you don't need to change to:
#Id
#Column(name = "username", unique = true, nullable = false, length = 50)
private String username;
The trick is you must start by loading from the DB, via em.find(...) or em.createQuery(...). Then the id is guaranteed to be populated with the right value from the DB.
Then you can detach the entity by ending a transaction (for a transaction-scoped entity manager in a session bean), or by calling em.detach(ent) or em.clear(), or by serialising the entity and passing it over the network.
Then you can update the entity, all the while, keeping the original id value.
Then you can call em.merge(ent) and you will still have the correct id. However, I believe the entity must already pre-exist in the persistent context of the entity manager at this instant, otherwise it will think that you have a new entity (with manually populated id), and try to INSERT on transaction flush/commit.
So the second trick is to ensure the entity is loaded at the point of the merge (via em.find(...) or em.query(...) again, if you have a new persistent context and not the original).
:-)