JPA add new entity to collection, after transaction, the entity still has no id - spring

UserInfoEntity user = userRepository.findOne(1L);
ActivityInfoEntity entity = new ActivityInfoEntity();
entity.setUser(user);
user.getActivities().add(entity);
userRepository.save(user);
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
#Override
public void afterCommit() {
System.out.println(entity.getId());// null
System.out.println(user.getActivities().size());// 1
}
});
Even after commit, the entity has no id, but add to collection create a new entity.
If I do activityRepository.save(entity); will add double to collectionHHH-6776.I need entity.getId() in this function to do something else.If I return entity, the returned entity will have an id, what's wrong ?
EDIT
saveAndFlush still null
Spring-boot 1.3.3
EDIT
The service is annotated with #Transactional.
#org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
#Entity
#EntityListeners(AuditingEntityListener.class)
#Table(name = "activity_info")
#Where(clause = "is_del = 0")
public class ActivityInfoEntity{
#Id
#GeneratedValue
private Long id;
#org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
#Fetch(FetchMode.SUBSELECT)
#OneToMany(mappedBy = "activity", cascade = CascadeType.ALL, orphanRemoval = true)
List<UserInfoEntity> users;
}
#org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
#Entity
#EntityListeners(AuditingEntityListener.class)
#Table(name = "user_info")
#Where(clause = "is_del = 0")
public class UserInfoEntity{
#Id
#GeneratedValue
private Long id;
#ManyToOne(optional = false, fetch = FetchType.LAZY)
#JoinColumn(name = "activity_id", nullable = false, updatable = false)
private ActivityInfoEntity activity;
}
EDIT
HHH-6776 Hibernate inserts duplicates into #OneToMany collection

Related

JPA Repository.findByKeyEquals() returns not present value, but the value is exist on db

I'm developing an application that queries a database.
There are a few issues right now.
history.isPresent() == false when calling Optional<History> findByKeyEquals() intermittently. but value is exist on database
This is the information I got while tracking the issue.
All child entities are non-null.
In most cases, if the same function is re-executed, it is searched.
But sometimes it doesn't happen intermittently.
i think that i use incorrectly table relationship annotation (#OneToMany,#ManyToOne options..)
I want to solve this issue.
this is my code
History (Parent)
#Table(
indexes = {
#Index(columnList = "key", unique = true),
})
#Entity
#Getter
#ToString
#Audited
public class History implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(updatable = false, nullable = false, columnDefinition = "BIGINT UNSIGNED")
private Long id;
#Setter
#Column(nullable = false, columnDefinition = "CHAR(36)")
private String key = UUID.randomUUID().toString();
#Setter
#Temporal(TemporalType.TIMESTAMP)
#NotAudited
private Date started = new Date();
#Setter
#Temporal(TemporalType.TIMESTAMP)
#NotAudited
private Date finished;
#Setter
#OneToMany(
cascade = CascadeType.ALL,
fetch = FetchType.LAZY,
mappedBy = "history",
orphanRemoval = true)
#NotAudited
private List<Content> contents = new ArrayList<>();
...
}
Content (Child)
#Table
#Entity
#Getter
#Audited
public class Content implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(updatable = false, nullable = false, columnDefinition = "BIGINT UNSIGNED")
private Long id;
#Setter
#Column(columnDefinition = "LONGTEXT")
#NotAudited
private String content;
#Setter
#ManyToOne(targetEntity = History.class, fetch = FetchType.Lazy, optional = false)
#Audited
private History history;
...
}
Repository
public interface HistoryRepository
extends JpaRepository<History, Long>, RevisionRepository<History, Long, Integer> {
Optional<History> findByKeyEquals(final String key);
}

Infinite JSON in ManyToMany relationship mapped by Intermediary Table

I have 2 entities that relate to one another. These 2 entities should map to each other in a Many-To-Many relationship, however, I need to also have a timestamp of their respective relationship (when it happened), so I am trying to map them using an intermediary table.
Initially, the relationship was One-To-Many, but I realized that I actually need a Many-To-Many as the business logic requires this. The structure is still the same, as in there is a Parent-Child relationship, but this time, a child should have multiple parents as well.
My BaseEntity is an abstract class that contains the fields present in all the other entities:
#Data
#MappedSuperclass
public abstract class BaseEntity {
#Id
#Min(100)
#Max(Integer.MAX_VALUE)
#GeneratedValue(strategy = GenerationType.IDENTITY)
protected Long id;
#CreationTimestamp
#Column(name = "Created_At", updatable = false)
protected ZonedDateTime createdDate;
#UpdateTimestamp
#Column(name = "Updated_At")
protected ZonedDateTime updatedDate;
#NotNull
#Column(name = "Is_Active")
protected Boolean active = true;
}
Then I have my 2 entities that should relate in a Many-To-Many style. This is my first entity and should be the parent:
#Data
#Entity
#NoArgsConstructor
#AllArgsConstructor
#Table(name = "User")
#EqualsAndHashCode(callSuper = true)
#TypeDefs( {
#TypeDef(name = "json", typeClass = JsonStringType.class),
#TypeDef(name = "jsonb", typeClass = JsonBinaryType.class)
})
public class UserEntity extends BaseEntity {
#NotBlank
#Column(name = "User_Name", columnDefinition = "varchar(255) default 'N/A'")
private String userName;
#Nullable
#JoinColumn(name = "User_Id")
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<UserRole> roleList = new ArrayList<>();
}
My second entity is considered the child entity:
#Data
#Entity
#NoArgsConstructor
#AllArgsConstructor
#Table(name = "Role")
#Where(clause = "is_active = true")
#EqualsAndHashCode(callSuper = true)
public class RoleEntity extends BaseEntity {
#NotBlank
#Column(name = "Name")
private String name;
#JsonIgnore
#JoinColumn(name = "Role_Id")
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<UserRole> userList = new ArrayList<>();
}
I also have my intermediary entity:
#Data
#Entity
#Getter
#NoArgsConstructor
#AllArgsConstructor
#Where(clause = "is_active = true")
#EqualsAndHashCode(callSuper = true)
#Table(name = "User_Role", uniqueConstraints= #UniqueConstraint(columnNames={"User_Id", "Role_Id"}))
public class UserRole extends BaseEntity {
// Adding #JsonIgnore here will only cause an error
#JoinColumn(name = "User_Id")
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY, optional = false, targetEntity = UserEntity.class)
private UserEntity user;
#JoinColumn(name = "Role_Id")
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY, optional = false, targetEntity = RoleEntity.class)
private RoleEntity role;
}
Problem now is that when I try to get my UserEntity, I get infinite recursion.
So far I've tried using #JsonIgnore, #JsonManagedReference, #JsonBackReference and it did not work or I simply don't know where or how to use them properly.
Recap:
2 entities mapped by Many-To-Many relationship;
Many-To-Many implemented using an intermediary entity and One-To-Many + Many-To-One associations;
Getting recursion when showing my UserEntity;
Update: I managed to get this fixed using a different approach described in my answer to this question.
I fixed this by implementing a Composite Key structure and just using the #JsonIgnore annotation:
#Getter
#Setter
#Embeddable
#EqualsAndHashCode
#NoArgsConstructor
#AllArgsConstructor
public class UserRoleKey implements Serializable {
#Column(name = "User_Id")
Long userId;
#Column(name = "Role_Id")
Long roleId;
}
This gets to be used in the intermediary entity, which now doesn't use my BaseEntity anymore.
#Data
#Entity
#NoArgsConstructor
#AllArgsConstructor
#Table(name = "User_Role", uniqueConstraints= #UniqueConstraint(columnNames={"User_Id", "Role_Id"}))
public class UserRole {
#JsonIgnore
#EmbeddedId
private UserRoleKey id;
#JsonIgnore
#MapsId("userId")
#JoinColumn(name = "User_Id")
#ManyToOne(optional = false, targetEntity = UserEntity.class)
private UserEntity user;
#MapsId("roleId")
#JoinColumn(name = "Role_Id")
#ManyToOne(optional = false, targetEntity = RoleEntity.class)
private RoleEntity role;
#CreationTimestamp
#Column(name = "Created_At", updatable = false)
private ZonedDateTime createdDate;
}
Now, for my two entities, I have this definition:
UserEntity class (definition of the role):
#Nullable
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "user", orphanRemoval = true)
private List<UserRole> roleList = new ArrayList<>();
RoleEntity class (definition of the user)
#Nullable
#JsonIgnore
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "role", orphanRemoval = true)
private List<UserRole> userList = new ArrayList<>();
This seems to be working and no longer returns an infinite JSON recursion.

Hibernate deletion referential integrity constraint violation on many to many association

I am trying to use Hibernate to remove an entity however I get an error: Cannot delete or update a parent row: a foreign key constraint fails
The setup is that I have an abstract class A and two classes (B and C) which extend A. B contains a list of C's (unidirectional relationship). And there is a function to delete A by its ID.
Note: Stuff has been removed for brevity.
#Entity
public class B extends A {
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(
joinColumns = #JoinColumn(name = "B_A_id"),
inverseJoinColumns = #JoinColumn(name = "C_A_id"))
List<C> cList;
}
#Entity
public class C extends A {
(no reference to B)
}
The issue is that when the deleteAByFixedId is called where A is a C, it tries to delete the C before it deletes the B which references it and therefore I get a foreign key constraint failure.
What am I doing wrong?
The answer will still be updated.
Links:
The best way to use the #ManyToMany annotation with JPA and Hibernate
Hibernate Inheritance Mapping
#ManyToMany
Unidirectional example:
User.java
#Entity
public class User {
#Id
#GeneratedValue
#Column(name = "user_id")
private long id;
...
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinTable(name = "user_role", joinColumns = #JoinColumn(name = "user_id"), inverseJoinColumns = #JoinColumn(name = "role_id"))
private Set<Role> roles = new HashSet<>();
public void addRoles(Role role) {
roles.add(role);
}
public void removeRoles(Role role) {
roles.remove(role);
}
}
Role.java
#Entity
public class Role {
#Id
#GeneratedValue
#Column(name = "role_id")
private int id;
#Column(name = "role")
private String role;
}
Bidirectional example:
Trader.java:
#Data
#AllArgsConstructor
#NoArgsConstructor
#Entity
#ToString(exclude = "stockmarkets")
#Table(name = "trader")
public class Trader {
#Id
#GeneratedValue
#Column(name = "trader_id")
private Long id;
#Column(name = "trader_name")
private String traderName;
#ManyToMany(fetch = FetchType.LAZY,
cascade = {
CascadeType.PERSIST,
CascadeType.MERGE
})
#JoinTable(name = "TRADER_STOCKMARKET",
joinColumns = { #JoinColumn(name = "trader_id") },
inverseJoinColumns = { #JoinColumn(name = "stockmarket_id") })
private Set<Stockmarket> stockmarkets = new HashSet<>();
/*
We need to add methods below to make everything work correctly
*/
public void addStockmarket(Stockmarket stockmarket) {
stockmarkets.add(stockmarket);
stockmarket.getTraders().add(this);
}
public void removeStockmarket(Stockmarket stockmarket) {
stockmarkets.remove(stockmarket);
stockmarket.getTraders().remove(this);
}
}
Stockmarket.java
#Data
#AllArgsConstructor
#NoArgsConstructor
#Entity
#ToString(exclude = "traders")
#Table(name = "stockmarket")
public class Stockmarket{
#Id
#GeneratedValue
#Column(name = "stockmarket_id")
private Long id;
#Column(name = "stockmarket_name")
private String stockmarketName;
#ManyToMany(mappedBy="stockmarkets")
private Set<Trader> traders = new HashSet<>();
/*
We need to add methods below to make everything work correctly
*/
public void addTrader(Trader trader) {
traders.add(trader);
trader.getStockmarkets().add(this);
}
public void removeTrader(Trader trader) {
traders.remove(trader);
trader.getStockmarkets().remove(this);
}
}

Jackson #JsonIgnoreProperties seems not to work all the time

I mapped two entities to those following classes :
#Getter
#Entity
#Table(name = "users")
#JsonIdentityInfo(generator = PropertyGenerator.class,
property = "id")
#SequenceGenerator(name = "id-generator", sequenceName = "seq_users")
#EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false)
#NoArgsConstructor(access = PROTECTED)
#RequiredArgsConstructor
public class User extends IdentifiedById {
#Include
#NonNull
#Column(name = "email_address", unique = true)
private String emailAddress;
#Setter
#JsonIgnore
private String hash;
#Setter
private boolean admin;
#OneToMany(
mappedBy = "user",
orphanRemoval = true,
cascade = ALL
)
#JsonIgnoreProperties("user")
private Set<Cart> carts;
{
carts = new HashSet<>(0);
}
}
#Getter
#Entity
#Table(
name = "carts",
uniqueConstraints = #UniqueConstraint(
columnNames = {
"creation_time",
"user_id"
}
)
)
#JsonIdentityInfo(generator = PropertyGenerator.class,
property = "id")
#SequenceGenerator(
name = "id-generator",
sequenceName = "seq_carts"
)
#EqualsAndHashCode(
callSuper = false
)
#RequiredArgsConstructor
#NoArgsConstructor(access = PROTECTED)
public class Cart extends IdentifiedById {
#NonNull
#Column(name = "creation_time")
private LocalDateTime creationTime;
#NonNull
#ManyToOne(cascade = ALL)
#JoinColumn(
name = "user_id",
referencedColumnName = "id"
)
#JsonManagedReference
private User user;
#Exclude
#JsonProperty("productStoreQuantities")
#JsonSerialize(converter = AdditionConverter.class)
#OneToMany(mappedBy = "cart", orphanRemoval = true, cascade = ALL)
private Set<Addition> additions;
{
additions = new HashSet<>(0);
}
}
If I retrieve a user, its carts do not contain its reference, it is fine by me.
Now from a rest endpoint perspective I would like not to serialize users along with their carts if one requests multiple users like so :
**/api/users -> {"id":1, "emailAddress":"test#test.test", "admin": false}**
**/api/users/1 -> {"id":1, "emailAddress":"test#test.test", "admin": false, "carts": [...]}**
Thus, I created a wrapper class named Users containing a list of users annotated with #JsonValue and #JsonIgnoreProperties("carts") :
#RequiredArgsConstructor
public class Users implements Serializable, List<User> {
#Delegate
#JsonValue
#JsonIgnoreProperties("carts")
private final List<User> values;
}
I don't know why but carts keep being serialized, I heard that #JsonIgnoreProperties does not work on collections and arrays but it does in my first case.
You should use JsonIgnoreProperties in a class level.
This is well explained in this post
https://www.baeldung.com/jackson-ignore-properties-on-serialization

HIbernate + JPA OneToMany Lazy loading not working if no foreign key specified in the db

Hibernate lazy loading is not working in my code. It loads the entire data even it is specified as FetchType LAZY
#Transactional(propagation = Propagation.NEVER)
public OrderItem getItem(String itemId) throws Exception {
OrderItem item = itemDao.find(OrderItem.class, Integer.parseInt(itemId));
if (item == null) {
throw new Exception(502, "We are unable to load item for #" + itemId);
}
return item;
}
#NotFound(action = NotFoundAction.IGNORE)
#OneToMany(cascade={CascadeType.PERSIST, CascadeType.MERGE}, fetch = FetchType.LAZY)
#JoinColumn(name = "id_order_detail")
#Fetch(value= FetchMode.JOIN)
#JsonManagedReference
private Set<OrderItemStateChangeEntry> itemStateHistory;
I could not able to lazy load the contents. There is no foreign key constraint set in the db. And its not possible to set as the many parent data not present in the system.
Can somebody help me on this
Update
Added my class and reference. But lazy load work
#Entity
#Table(name = "ps_orders")
#AttributeOverrides({
#AttributeOverride(name="id",column=#Column(name="id_order")),
#AttributeOverride(name="createTime",column=#Column(name="date_add")),
#AttributeOverride(name="updateTime",column=#Column(name="date_upd"))
})
public class Order extends BaseEntity{
#Column(name = "id_carrier")
private Integer carrier = 0;
#NotFound(action = NotFoundAction.IGNORE)
#OneToMany(fetch = FetchType.LAZY,cascade={CascadeType.PERSIST, CascadeType.MERGE}, mappedBy="order")
#Fetch(FetchMode.SELECT)
#JsonManagedReference
private Set<OrderStateChangeEntry> orderHistory;
//Getters and Setters
}
#Entity
#Table(name = "ps_order_history")
#EnableBouncerProfile
public class OrderStateChangeEntry implements java.io.Serializable{
public OrderStateChangeEntry(){}
public OrderStateChangeEntry(Order order){
this.order = order;
}
#Id
#Column(name = "id_order_history")
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name="id_order", nullable=false)
#JsonBackReference
private Order order;
//Getters and Setters
}
It is because of your
#Fetch(value= FetchMode.JOIN)
it disables the lazy loading ...
As you specify the fetch mode in your #OnetoMany relationship, i would say that you can simply remove that line above.

Resources