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);
}
}
Related
#Getter #Setter #NoArgsConstructor #AllArgsConstructor
#Table(name = "my_users")
public class MyUsers {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(nullable = false)
private Long id;
#Column(nullable = false, unique = true)
private String userName;
private String password;
#ManyToMany
private List<MyUsers> connections;
}
This is my MyUsers Model Class. I am using Hibernate and MySQL.
#ManyToMany
private List<MyUsers> connections;
This ManyToMany relationship is automatically creating the table 'my_users_connections' with 'my_users_id' and 'connections_id' colums. How can I add extra columns to this auto mapped table?
It's not ideal solution...
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#Entity
#Table(name = "my_users")
public class MyUsers implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(nullable = false)
private Long myUsersId;
#Column(nullable = false, unique = false)
private String userName;
private String password;
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "my_users_connections",
joinColumns = { #JoinColumn(name = "my_users_id") },
inverseJoinColumns = { #JoinColumn(name = "connections_id") })
private List<MyUsers> connections;
}
Create embedded id MyUsersConnectionsPK:
#Data
#Embeddable
public class MyUsersConnectionsPK implements Serializable {
#Column(name = "my_users_id")
private Long myUsersId;
#Column(name = "connections_id")
private Long connectionsId;
}
Create MyUsersConnections, which represent ManyToMany
#Data
#Entity
#Table(name = "my_users_connections")
public class MyUsersConnections implements Serializable {
#EmbeddedId
private MyUsersConnectionsPK id;
#ManyToOne
#MapsId("my_users_id")
#JoinColumn(name = "my_users_id")
private MyUsers myUsersId;
#ManyToOne
#MapsId("connections_id")
#JoinColumn(name = "connections_id")
private MyUsers connectionsId;
#Column(name = "extra_column")
private String extraColumn;
}
Create JPA repository
#Repository
public interface MyUsersConnectionsRepository extends JpaRepository<MyUsersConnections, MyUsersConnectionsPK> {
List<MyUsersConnections> findMyUsersConnectionsByMyUsersIdMyUsersId(Long id);
}
And simple sample for using:
#Service
public class Test {
#Autowired
private MyUsersConnectionsRepository myUsersConnectionsRepository;
#Autowired
private MyUsersRepository myUsersRepository;
public void test() {
MyUsers myUsers = new MyUsers();
myUsers.setUserName("user name");
myUsers.setPassword("password");
MyUsers myUsers2 = new MyUsers();
myUsers2.setUserName("user name 2");
myUsers2.setPassword("password 2");
myUsers.setConnections(Collections.singletonList(myUsers2));
myUsers = myUsersRepository.saveAndFlush(myUsers);
List<MyUsersConnections> myUsersConnections = myUsersConnectionsRepository.findMyUsersConnectionsByMyUsersIdMyUsersId(myUsers.getMyUsersId());
MyUsersConnections item = myUsersConnections.get(0);
item.setExtraColumn("Extra column");
myUsersConnectionsRepository.saveAndFlush(item);
}
}
I have a entity call Circuit.
#Entity
public class Circuit implements Comparable<Circuit>, Serializable {
#Column
private String id;
#OneToMany(mappedBy = "circuit", cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
private Set<Step> workflow = new HashSet<>();
...
}
I have a class called CircuitLight
public class CircuitLight {
private String id;
private Set<Step> workflow;
/* constructor, getters and setters */
}
In my CircuitRepository, i'm trying to make a projection
#Transactional(readOnly = true)
#Query("select new com.docapost.circuit.CircuitLight(c.id, c.workflow) from Circuit c where c.account.siren = :siren")
Set<CircuitLight> findAllByAccountSirenProjection(#Param("siren") String siren);
When i execute, i have a error message:
could not extract ResultSet; SQL [n/a] com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Unknown column 'circuit0_.id' in 'on clause'
I try with other entity. Every time i have a property with a relation #OneToMany, i have the issue...
Is it possible to make a projection with class (Without use a interface) when there are a relation OneToMany ?
UPDATE:
Step.class
#Entity
public class Step implements Comparable<Step>, Serializable {
private static final List<String> INDEXABLE_PROCESSES = Arrays.asList(
ParapheurWorkflowModel.SERVER,
ParapheurWorkflowModel.SIGN,
ParapheurWorkflowModel.VISA
);
#Id
#GeneratedValue
#Expose
#SerializedName("step_id")
public long id;
#ManyToOne
public Circuit circuit;
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(joinColumns = #JoinColumn(name = "step_id"), inverseJoinColumns = #JoinColumn(name = "technicalGroup_id"))
private List<TechnicalGroup> technicalGroups = new ArrayList<>();
#Column(name = "step_type", nullable = false)
#Expose
#SerializedName("subprocess_ref")
public String type;
#Column(nullable = false)
public int orderIndex;
/* contructor, getters and setters */
}
UPDATE 2:
Hum.... My bad, in my circuit class, i have a EmbeddedId
#EmbeddedId
private CircuitPK key;
#Embeddable
public static class CircuitPK implements Serializable {
public String id;
public String siren;
}
I try with this code in Step.class
#ManyToOne
#JoinColumns(value = {
#JoinColumn(name = "circuit_siren", referencedColumnName = "siren"),
#JoinColumn(name = "circuit_id", referencedColumnName = "id")
})
public Circuit circuit;
The result is the same
Write the following code in the Step entity
#ManyToOne
#JoinColumn(name="id", nullable=false)
private Circuit circuit;
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.
I have the below room entity which has many disponibilities, when i add a room with a list of disponibilities, the room is saved but the list is not. what am i missing in the relationship ?
#Entity
public class RoomEntity {
#Id
private String classRoomId;
private String label;
#OneToMany(mappedBy = "room", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private List<DisponibilityEntity> disponibilities;
public void addDisponibilities(List<DisponibilityEntity> disponibilityEntities) {
if (CollectionUtils.isEmpty(disponibilities)) {
disponibilities = new ArrayList<>();
}
disponibilities.addAll(disponibilityEntities);
disponibilityEntities.forEach(item -> item.setRoom(this));
}
}
#Entity
public class DisponibilityEntity {
#Id
private String disponibilityId;
#JsonIgnore
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "fk_room")
private RoomEntity room;
}
roomEntity.addDisponibilities(classRoomEntity.getDisponibilities());
roomRepository.save(roomEntity);
I have a class called Tag:
#Entity
#Table(name = "tags")
public class Tag {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#ManyToMany(fetch = FetchType.LAZY,
cascade = {
CascadeType.PERSIST,
CascadeType.MERGE
},
mappedBy = "tags")
private Set<Post> posts = new HashSet<>();
...
}
And a class called Post
#Entity
#Table(name = "posts")
public class Post {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToMany(fetch = FetchType.LAZY,
cascade = {
CascadeType.PERSIST,
CascadeType.MERGE
})
#JoinTable(name = "post_tags",
joinColumns = { #JoinColumn(name = "post_id") },
inverseJoinColumns = { #JoinColumn(name = "tag_id") })
private Set<Tag> tags = new HashSet<>();
...
}
It creates another table named post_tags.
How can I write a Controller to access that table as it is not similar a repository?
Is there more easy and convenient way to implement ManyToMany relationship ?
My pom.xml
You don't need to access that relation table manually. You can load load all Tag entities, and then load all the referenced Post entities.
The relation table is enterily managed by your ORM frameork.
But, if you still want to access the relation table, you can use native queries in your Spring Data JPA repository, e.g.
#Query(value="select post_id, tag_id from post_tags", nativeQuery=true)
List<PostTag> loadPostTags();
PostTag class is not a jpa-managed entity and must match the structue of the returned table:
public class PostTag {
private long postId;
private long tagId;
// getter, setter
}
Use this way
#Entity
#Table(name = "tags")
public class Tag {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "post_tags",
joinColumns = { #JoinColumn(name = "id") },
inverseJoinColumns = { #JoinColumn(name = "post_id") })
private Set<Post> posts = new HashSet<>();
...
}
#Entity
#Table(name = "posts")
public class Post {
#Id
#Column(name = "post_id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long postId;
...
}