JPA Composite PK referenced by FKs in One to Many Relationship - spring

I am trying to map the tables below into JPA. The relationships are one-to-many between user_tax and tax and user_tax and user. It has confused me the fact that i have a composite primary key, and i need to map the foreign keys to these 2 keys.
the error message: org.hibernate.AnnotationException: mappedBy reference an unknown target entity property: entity.Tax.user_tax in entity.UserTax.taxs
tax user_tax user
-------- -------- ------
PK|t_id |--------| t_id |PK-FK |u_name|
|t_name| PK-FK| u_id |-------|u_id | PK
| | | name | | |
Here is my Entities:
#Entity
#Table(name = "user")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public class User implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name="u_name")
private String uname;
getters + setters
}
#Entity
#Table(name = "tax")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public class Tax implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "t_name")
private String tname;
#Embeddable
public class UserTaxId implements Serializable {
#Column(name="u_id")
private Long uId;
#Column(name="t_id")
private Long tId;
#Entity
#Table(name = "user_tax")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public class UserTax implements Serializable {
#EmbeddedId
private UserTaxId userTaxId;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "user_tax")
private List<User> users;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "user_tax")
private List<Tax> taxs;

Your 1:n mappings are backwards (i.e. a UserTax can have only a single User and a single Tax) and you are using a derived identity. Try mapping UserTax like this:
#Entity
#Table(name = "user_tax")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public class UserTax implements Serializable {
#EmbeddedId
private UserTaxId userTaxId;
#ManyToOne(fetch = FetchType.LAZY)
#MapsId("uId") // maps uId attribute of embedded id
private User user;
#ManyToOne(fetch = FetchType.LAZY)
#MapsId("tId") // maps tId attribute of embedded id
private Tax tax;
...
}
Derived identities are discussed (with examples) in the JPA 2.2 spec in section 2.4.1.

I will post here what worked for me after 3 days of research.
Brian Vosburgh correctly posted the UserTax class:
#Entity
#Table(name = "user_tax")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public class UserTax implements Serializable {
#EmbeddedId
private UserTaxId userTaxId;
#ManyToOne(fetch = FetchType.LAZY)
#MapsId("uId") // maps uId attribute of embedded id
private User user;
#ManyToOne(fetch = FetchType.LAZY)
#MapsId("tId") // maps tId attribute of embedded id
private Tax tax;
...
}
However, i was getting error meassages and my code wasn't compiling. Then i also had to edit the User and Tax classes:
#Entity
#Table(name = "user")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public class User implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToMany(
mappedBy = "tid",
cascade = CascadeType.ALL,
orphanRemoval = true
)
private List<UserTax> tax = new ArrayList<>();
#Column(name="u_name")
private String uname;
getters + setters
}
#Entity
#Table(name = "tax")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public class Tax implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToMany(
mappedBy = "uid",
cascade = CascadeType.ALL,
orphanRemoval = true
)
private List<UserTax> taxs = new ArrayList<>();
#Column(name = "t_name")
private String tname;
setters+getters
}
Here is the link where i found the solution to my problem: https://vladmihalcea.com/the-best-way-to-map-a-many-to-many-association-with-extra-columns-when-using-jpa-and-hibernate/

Related

Is there a way to self-reference an entity using Spring JPA

I'm using Spring JPA and MySQL as the database. I have trouble with self-referencing its own entity.
I know the code below would do self-referencing, but it actually creates a new table to do so.
#Getter
#Entity
#NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Member extends BaseTimeEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "member_id")
private Long id;
#ManyToMany
#JoinColumn(name = "followings_id")
private List<Member> followings = new ArrayList<Member>();
#ManyToMany
#JoinColumn(name = "followers_id")
private List<Member> followers = new ArrayList<Member>();
#ManyToMany
#JoinColumn(name = "blocked_id")
private List<Member> blocked = new ArrayList<Member>();
...
}
Question: I'm wondering if I can do this in a single table(which would be the member table) without creating a new table to do many-to-many self-referencing.
It is possible,
Instead of using the #ManyToMany annotation, you can use the #OneToMany and #ManyToOne annotations to create the self-referencing relationship
#Getter
#Entity
#NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Member extends BaseTimeEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "member_id")
private Long id;
#OneToMany(mappedBy = "follower")
private List<Follow> followings = new ArrayList<>();
#OneToMany(mappedBy = "following")
private List<Follow> followers = new ArrayList<>();
#OneToMany(mappedBy = "blocker")
private List<Block> blocked = new ArrayList<>();
}
#Entity
#Getter
#NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Follow {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne
#JoinColumn(name = "follower_id")
private Member follower;
#ManyToOne
#JoinColumn(name = "following_id")
private Member following;
}
#Entity
#Getter
#NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Block {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne
#JoinColumn(name = "blocker_id")
private Member blocker;
#ManyToOne
#JoinColumn(name = "blocked_id")
private Member blocked;
}
Now Follow and Block entities represent the many-to-many relationships between Member entities and follower and following properties in the Follow entity represent the two sides of the many-to-many relationship, and the same is for blocked and blocker.

How to implements entity with 2 entity as primary key with jpa annotation and repository

i want to implement a many to many association with quantity information in it . like this :
#Entity
#Table(name = "reserves")
#Getter #Setter #NoArgsConstructor
public class Reserve {
#Id
#ManyToOne(cascade = CascadeType.ALL,fetch = FetchType.EAGER)
#JoinColumn(name = "groupe_id")
private GroupeSanguin bloodGroup;
#Id
#ManyToOne(cascade = CascadeType.ALL,fetch = FetchType.EAGER)
private Banque banque;
private int quantity;
}
the GroupSanguin and the Banque are two class stored in the database two . here is the code for the two if you need :
#Entity
#Table(name = "groupe_sanguins")
public class GroupeSanguin {
#Id
private String groupe;
#OneToMany(mappedBy = "groupeSanguin")
private List<Donneur> donneurs;
}
#Entity #Getter #Setter #NoArgsConstructor
public class Banque {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(unique = true,nullable = false)
private String nom;
private String adresse;
#Column(unique = true)
private String telephone;
private String localisation;
}
so my i want to know how to annotate the JpaRepository to take the two as primary key like this and is my annotation good for it to work ?
public interface ReserveRepository extends JpaRepository<
Reserve,
//what to put here ?
>
This isn't a JPA question in fact, it's a relationnal database conception.
If Reserve has is own data and links with other entity it has it own Id
You can add unicity constraint
#Entity
#Table(name = "reserves", uniqueConstraints={
#UniqueConstraint(columnNames = {"banque_id", "groupe_id"})
#Getter #Setter #NoArgsConstructor
public class Reserve {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne(cascade = CascadeType.ALL,fetch = FetchType.EAGER)
#JoinColumn(name = "groupe_id")
private GroupeSanguin bloodGroup;
#ManyToOne(cascade = CascadeType.ALL,fetch = FetchType.EAGER)
#JoinColumn(name = "banque_id")
private Banque banque;
private int quantity;
}
i've found this solutions too.
#Entity
#Table(name = "reserves")
#Getter #Setter #NoArgsConstructor
#IdClass(ReserveId.class) //this annotation will tell that id that the
// the id will be represented by a class
public class Reserve {
#Id
#ManyToOne(cascade = CascadeType.ALL,fetch = FetchType.EAGER)
#JoinColumn(name = "groupe_id")
private GroupeSanguin groupeSanguin;
#Id
#ManyToOne(cascade = CascadeType.ALL,fetch = FetchType.EAGER)
#JoinColumn(name = "banque_id")
private Banque banque;
private int quantity;
}
and the id class should implements Serializable like this :
#Getter #Setter
public class ReserveId implements Serializable {
private Banque banque;
private GroupeSanguin groupeSanguin;
}
and finally the repository will be like that :
#Repository
public interface ReserveRepo extends JpaRepository<Reserve, ReserveId>{}
See your Reserve class has nowhere mentioned composite primary key. First you need to fix the model, You can refer to the solution here How to create and handle composite primary key in JPA

Add extra custom column to auto mapped Table in Spring JPA ManyToMany

#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);
}
}

#OnDelete not working correctly with #Inheritance and #ManyToOne combination

When I delete the D object I would like to delete associated data in C(including parent data) as well. In the below code when I deleted the D, C part deleted successfully but the parent(A) related part still exists in the database.
I have seen the https://hibernate.atlassian.net/browse/HHH-13299 issue, but I don't know it is the same issue or not, if yes then what could I do as a workaround?
#Entity
class D {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
}
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
public abstract class A {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
private String common;
}
#Entity
public class B extends A {
}
#Entity
public class C extends A {
#ManyToOne(optional = false, cascade = CascadeType.ALL)
#JoinColumn(name = "d_id")
#OnDelete(action = OnDeleteAction.CASCADE)
private D d;
}
Assume that getter and setters are implemented.

Entity not mapped to a single property error with inherited entites of one table

I have two entities SuperAlbumEntity and AlbumEntity reflecting the same table "albums".
SuperAlbumEntity:
#Entity
#Table(name = "albums")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public class SuperAlbumEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
//other fields
}
AlbumEntity:
#EqualsAndHashCode(callSuper = true)
#Entity
#Table(name = "albums")
public class AlbumEntity extends SuperEntity{
//some fields
#Column(name = "country")
private String country;
#OneToMany(fetch = FetchType.EAGER)
#JoinColumn(name = "country_name", referencedColumnName = "country")
private Set<CountryEntity> countrySet = new HashSet<>();
}
AlbumEntity has #OneToMany mapping to CountryEntity:
#Entity
#Table(name = "countries")
public class CountryEntity implements Serializable {
#Id
String id;
String country_name;
//other fields
}
Running my application I get the folowing error:
...
Caused by: org.hibernate.AnnotationException: referencedColumnNames(country) of CountryEntity.countrySet referencing AlbumEntity not mapped to a single property
...
What's interesting is that if I move country field from SuperAlbumEntity to AlbumEntity everything just works fine...
Can someone explain me why I get this error?
I'm not sure but I think is connected with the type of inherence that you used it. Try to modify your superclass to something like this:
SuperAlbumEntity:
#MappedSuperclass
public abstract class SuperAlbumEntity {
}
AlbumEntity:
#Entity
#Inheritance(strategy=InheritanceType.JOINED)
#Table(name = "albums")
public class AlbumEntity extends SuperEntity {
#OneToMany(fetch = FetchType.EAGER)
#JoinColumn(name = "country_name", referencedColumnName = "country")
private Set<CountryEntity> countrySet = new HashSet<>();
}

Resources