How to avoid updating child tables when using save Hibernate? - java

I have a parent table called USER and a child table called USERDATA linked with OneToMany. When I go to use the save method, if the child record exists it is updated. I would like it not to be updated but not added. What am I doing wrong?
My classes:
#Entity
#Table(name="USER")
#IdClass(UserPK.class)
public class User implements Serializable{
private static final long serialVersionUID = 1L;
#Id
#Column(name="USERID")
private String userId;
#Id
#Column(name="USERNUMBER")
private String userNumber;
private String name;
private String surname;
#OneToMany(mappedBy = "user", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
private List<UserData> userDatas;
#OneToMany(mappedBy = "user", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#Fetch(value = FetchMode.SUBSELECT)
private List<OtherData> otherDatas;
//getter and setter
}
UserData:
#Entity
#Table(name="USERDATA")
public class UserData implements Serializable{
private static final long serialVersionUID = 1L;
#Id
private String id;
private String City;
private String University;
#ManyToOne
#JoinColumns({
#JoinColumn(name = "USERID", referencedColumnName = "USERID"),
#JoinColumn(name = "USERNUMBER", referencedColumnName = "USERNUMBER")
})
private User user;
//getter and setter
OtherData:
#Entity
#Table(name="OTHERDATA")
public class OtherData implements Serializable{
private static final long serialVersionUID = 1L;
#Id
private String id;
private String hobby;
private String religion;
#ManyToOne
#JoinColumns({
#JoinColumn(name = "USERID", referencedColumnName = "USERID"),
#JoinColumn(name = "USERNUMBER", referencedColumnName = "USERNUMBER")
})
private User user;
//getter and setter
And my save:
session.save(obj); //obj is and User object
when I do the first insert everything is ok, when I do it again by changing the data of the primary key of user but not quelly of the primary key of userData or otherData, the data is updated. I don't want them updated.

You can not reuse the same Java object in this case. Also, changing the primary key should produce a big fat warning when flushing that object Hibernate.
If you want a new row to be inserted, you have to create a new object with new User(). Another option is to let Hibernate forget about the old object by using session.detach(user). The problem is, that Hibernate knows the object already and due to that, tries to update the existing row when you call save again.

Try setting the column like so:
#OneToMany(mappedBy = "user", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#Column(insertable = false, updatable = false)
private List<UserData> userDatas;
I haven't tested it but it should block inserts and updates via the entity. Of course you will need separate queries to insert update UserData somehow.
BTW I suggest not to use FetchType.EAGER, but always use FetchType.LAZY. If you need the child data to be fetched, just adapt the query you are using to do a FETCH JOIN, see here for an example:
SELECT FROM User u LEFT JOIN FETCH u.userData d

Hi Fesilox Please write this example
#JoinColumn(name = "USERID", referencedColumnName = "USERID", nullable = false,
insertable=false, updatable=false)

Related

JPA/Hibernate ManyToOne Association always null

I have two entities BookingLegEntity and BookingEntity which reference each other. But anytime I try to retrieve them from the database (e.g. via findByUuid), BookingLegEntity.belongsTo remains null.
Here are my entities:
#Entity
#Table(name = "BOOKING_LEG")
#SQLDelete(sql = "UPDATE BOOKING_LEG SET deleted = true WHERE id=?")
#Where(clause = "deleted=false")
public class BookingLegEntity {
#Id
#Column(name = "ID", unique = true, updatable = false)
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column(name = "UUID", nullable = false)
private UUID uuid;
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "BELONGS_TO")
private BookingEntity belongsTo;
// ..
#ManyToOne
#JoinColumn(name = "DISTRIBUTOR")
private DistributorEntity distributor;
#Column(name = "TRANSPORT_TYPE")
#Convert(converter = TripTypeEnumConverter.class)
private TripTypeEnum transportType;
// ...
}
#Entity
#Table(name="BOOKINGS")
#SQLDelete(sql = "UPDATE BOOKINGS SET deleted = true WHERE id=?")
#Where(clause = "deleted=false")
public class BookingEntity {
#Id
#Column(name="ID", unique=true, updatable = false)
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column(name="BOOKING_ID")
#Convert(converter = BookingIdConverter.class)
private BookingId bookingId;
#ManyToOne
#JoinColumn(name ="BOOKED_BY")
private UserEntity bookedBy;
// ..
#OneToMany(mappedBy = "belongsTo", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private Set<BookingLegEntity> bookingLegs = new HashSet<>();
// ...
}
Here is my repository:
#Repository
public interface BookingLegRepository extends JpaRepository<BookingLegEntity, Long> {
Optional<BookingLegEntity> findByUuid(UUID id);
// ...
}
The values in the database itself look correct:
What is really strange is that this has worked before (belongsTo was not null) but suddenly stopped working. Does anyone has any idea as to what we might do wrong here?
Do not use cascade = CASCADEType.ALL on your ManyToOne annotation, because removing one BookingLeg will cause a removal of all in corresponding Booking
The solution should be to use
cascade = CascadeType.DETACH,CascadeType.MERGE,CascadeType.PERSIST,CascadeType.REFRESH}) in its stead.
I would Truncate Cascade or Delete from Bookings where original_itinerary is null before i move on to using the new entities.
Sincerely hope it helps. (No hate if it doesn't pls)
Edit : i didnt see that comment by #dey, its my own. :P saw his comment after posting my ans

How to map JPA OneToMany relationship when FK is part of CompositeId

So I have a pre-existing Service Entity with multiple OneToMany relationships. Now I need to add one more but I am having trouble and I assume it must be because the Many side uses a Composite Key.
I have the Service.java with its new fields
#Column(name = "TRANSLATION_DV_ID")
private String translationDvId;
#OneToMany(cascade = CascadeType.All, fetch = FetchType.Eager, mappedBy = "service")
private List<Translation> translation;
and
#IdClass(TranslationId.class)
public class Translation {
#Id
#Column(name = "TRANSLATION_DV_ID")
private String translationDvId;
#Id
#Column(name = "LOCALE_CD")
private String localeCd;
#Column(name = "TRANSLATED_NAME")
private String translatedName;
#Column(name = "TRANSLATED_DESC")
private String translatedDesc;
#ManyToOne
#JoinColumn(name = "TRANSLATION_DV_ID", insertable = false, updatable = false)
private Service service;
The test data is generated with sql scripts. I entered the new data and matched the translationDvId's. The data is all present with the correct information except for Translation relationship- each Service always has an empty List<Translation>.
I am not sure what I am missing but here is an example of a data entry
INSERT INTO SCHEMA.SERVICE(SERVICE_CD, TRANSLATION_DV_ID, etc, etc)
VALUES ('servicePrimaryKey', '12345', 'etc, 'etc);
INSERT INTO SCHEMA.TRANSLATION(TRANSLATION_DV_ID, LOCALE_CD, TRANSLATED_NAME, TRANSLATED_DESC)
VALUES ('12345', 'English', 'Guardian', 'Cool stuff');
INSERT INTO SCHEMA.TRANSLATION(TRANSLATION_DV_ID, LOCALE_CD, TRANSLATED_NAME, TRANSLATED_DESC)
VALUES ('12345', 'Spanish', 'Guardia', 'Cosas interesantes');
#JoinColumn has a special property for when there is a Composite PK in the referenced table - referencedColumnName
#OneToMany
#JoinColumn(name = "TRANSLATION_DV_ID", referencedColumnName = "TRANSLATION_DV_ID")
private List<Translation> translation;

HIbernate ignore fetching data from OnetoMany field

I would like to ignore #OnetoMany field in my entity. fetch data need to get actual fields but don't want to fire query to dependent table. But deleting data from parent table needs deletion from dependent table
I have tried #Transient that ignores but the delete is also being ignored. Is there any other option to tell JPA not to fetch data from childs table when i call the parent entity?
#Entity
Table(name = "User")
public class UserEntity implements Serializable {
#Id
#Column(name = "id")
private int id;
#Column(name = "SERIAL", unique = true, nullable = false)
private String serial;
#OneToMany(mappedBy = "serialBySerialId", cascade = CascadeType.ALL)
private Set<UserActionEntity> userActionsById;
}
#Table(name = "user_action")
public class UserActionEntity implements Serializable {
#Id
#Column(name = "id")
private int id;
#Column(name = "action")
private String action;
#ManyToOne
#JoinColumn(name = "USER_ID", referencedColumnName = "ID", nullable = false)
private UserEntity userByUserId;
If you don't want to fire query to dependent table, you can use (fetch = FetchType.LAZY) on UserActionEntity property.
#OneToMany(mappedBy = "serialBySerialId", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Set<UserActionEntity> userActionsById;

Jpa: delete dependents instead of updating them

In my application every customer can have several accounts. I have the following data structure (a lot omitted for brevity):
#Entity
#Table(name = "CUSTOMER")
public class Customer {
#Id
#Column(length = 36, name = "CUSTOMER_ID", nullable = false, unique = true)
private String id;
#OneToMany
#JoinColumn(name = "OWNER_ID", referencedColumnName = "CUSTOMER_ID")
private List<Account> accounts;
}
#Entity
#Table(name = "ACCOUNT")
public class Account {
#Id
#Column(length = 36, name = "ACCOUNT_ID", nullable = false, unique = true)
private String id;
#Column(name = "OWNER_ID", nullable = false)
private String ownerId;
}
If I use JPA to delete a Customer, such as
entityManager.remove(customer);
it tries to update the related ACCOUNT.OWNER_ID fields with null. OWNER_ID is not nullable, so it throws a JDBCException and rolls back the transaction.
What I need to achieve is that the related ACCOUNT rows get deleted (if any). How can I do that?
Thank you
Update: I tried it with
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
but it does not change the behavior: still tries to update with null.
I think you need to be using cascading in order to remove the child elements. Try this:
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "OWNER_ID", referencedColumnName = "CUSTOMER_ID")
private List<Account> accounts;
You should also reference the Customer in your account by a ManyToOne relationship and not the String id. I think this should solve your issue:
#Column(name = "OWNER_ID", nullable = false)
#ManyToOne
private Customer owner;

What hibernate / jpa annotation is required

I am trying to create a new User(entity1) - it has reference to a Group (entity2) via a link table Member (entity3)
A user has a Set of groups as a class variable.
When i create my user object i want to say this user will be a member of group n (there are pre defined users that are linked to by id (1,2,3,4,5,6...) each group has some associated data in the table.
Whenever I create my user object as follows;
User user = new User();
user.setActive(1);
user.setCrby("me");
user.setUsername("username");
user.setCrdate("2016-06-20 12:42:53.610");
user.setCrwsref("...");
user.setModby("...");
user.setModdate("2016-06-20 12:42:53.610");
user.setModswref("..");
user.setBackground("Y");
user.setPassword("password");
user.setFullName("me");
Group group = new Group();
group.setId(1);
Group group2 = new Group();
group2.setId(2);
Set<Group> sets = new HashSet<Group>();
sets.add(group);
sets.add(group2);
user.setGroups(sets);
userDao.addUser(user);
I keep getting errors telling me that certain columns cannot be null. What I actually want to happen here is not to be doing an insert in to the group table but associating a user to a line in the group table. Is there a particular way I can prevent the columns in the group table being modified? I think I need to modify the mappings between the link table - this is how much pojos link right now
User
#Entity
#Table(name = "user")
public class User
{
#Id
#GeneratedValue
#Column(name = "id")
private int id;
#Column(name = "username")
private String username;
#Column(name = "password")
private String password;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name = "zmember", joinColumns = {#JoinColumn(name = "username")}, inverseJoinColumns = {#JoinColumn(name = "id")})
private Set<Group> groups = new HashSet<Group>(0);
Member link table
#Entity
#Table(name = "member")
public class Member implements Serializable
{
#Id
#GeneratedValue
#Column(name = "id")
private int id;
#Id
#Column(name = "sgpid")
private int sgpid;
#Column(name = "username")
private String memberUsername;
Group
#Entity
#Table(name = "group")
public class Group
{
#Id
#GeneratedValue
#Column(name = "id")
private int id;
What is happening is there is no association to the link Member table so ideally should User have a set of member objects rather than a set of groups?
Thanks - this was quite hard to explain so sorry if it is hard to understand
This is a typical case for the #ManyToMany annotation. See for example:
https://dzone.com/tutorials/java/hibernate/hibernate-example/hibernate-mapping-many-to-many-using-annotations-1.html
The relationship from User to Group is essentially ManyToMany. You could model this is using the #ManyToMany annotation however one drawback with this approach is you cannot save additional information about the group in the join table such as 'date_joined'.
See: https://en.wikibooks.org/wiki/Java_Persistence/ManyToMany#ManyToMany
Using this approach you would not need the Join entity Member and the relationship on User would look like:
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name = "zmember", joinColumns = {#JoinColumn(name = "member_id", referencedColumnName = "id")}, inverseJoinColumns = {#JoinColumn(name = "group_id", referencedColumnName = "id")})
private Set<Group> groups = new HashSet<Group>(0);
The alternative to using #ManyToMany is to use a Join entity Member(ship) as you have done. This would allow you to save additional data about the relationship (by defining additional field mappings in the Join entity).
In this case the mappings would look like:
User:
public class User{
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private Set<Membership> memberships = new HashSet<Membership>(0);
//if required, you can 'hide' the join entity from client code by
//encapsulating add remove operations etc.
public void addToGroup(Group group){
Membership membershup = new Membership();
membership.setUser(this);
membership.setGroup(group);
memberships.add(membership);
)
public Set<Groupp> getGroups(){
//iterate memberships and build collection of groups
}
}
Membership:
public class Membership{
#Id
#GeneratedValue
#Column(name = "id")
private int id;
#ManyToOne
#JoinColumn(name = "user_id")
private Member member;
#ManyToOne
#JoinColumn(name = "group_id")
private Group group;
}
Group:
#Entity
#Table(name = "group")
public class Group
{
#Id
#GeneratedValue
#Column(name = "id")
private int id;
#OneToMany(mappedBy = "group", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private Set<Membership> memberships = new HashSet<Membership>(0);
}

Categories