Using JPA #ElementCollection for a String collection from a join table? - jpa

I would like my User entity to include a List<String> of permissions coming from user_permission join table, however getting a multitude of errors using different examples.
Should ElementCollection also be able to handle join table as defined below given I indicate which columns to join?
user table
id (pk)
email
permission table
permission (pk)
permission_description
user_permission table
email (fk, pk)
permission (fk, pk)
User Entity
#ElementCollection
#Column(name="permission", nullable=false)
#CollectionTable(name = "user_permission", joinColumns = #JoinColumn(name = "email", referencedColumnName = "email"))
private List<String> permissions = new ArrayList<String>();
Error:
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is org.springframework.security.authentication.InternalAuthenticationServiceException: class com.abc.data.entity.User cannot be cast to class java.io.Serializable (com.abc.data.entity.User is in unnamed module of loader 'app'; java.io.Serializable is in module java.base of loader 'bootstrap')
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014) ~[spring-webmvc-5.2.6.RELEASE.jar:5.2.6.RELEASE]
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909) ~[spring-webmvc-5.2.6.RELEASE.jar:5.2.6.RELEASE]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:523) ~[jakarta.servlet-api-4.0.3.jar:4.0.3]
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883) ~[spring-webmvc-5.2.6.RELEASE.jar:5.2.6.RELEASE]

The issue was the join column, which in this case was email, which was not an ID on the User table.
When the user_permission table was altered to include the user ID instead of user email, and join table name modified accordingly, it works.
Working Example
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private int accountId;
private String name;
private String email;
private String password;
private boolean admin;
#ElementCollection
#Column(name="permission", nullable=false)
#CollectionTable(name = "user_permission", joinColumns = #JoinColumn(name = "user_id", referencedColumnName = "id"))
private List<String> permissions = new ArrayList<String>();

Related

JPA expecting ID column on joined table

I'm attempting to represent a join table with JPA, however the generated SQL is expecting an id field on one of the tables.
The generated SQL is very close, but appears an ID column is expected on Permission table and used as the FK on the join table, which I was hoping not to do. See schema below:
You can see I am not using an ID column on permission table, instead letting the text representation of the permission be the key.
User.java
#Entity
#Data
#Table(name = "user")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private int accountId;
private String name;
private String email;
private String password;
private boolean admin;
#ManyToMany
#JoinTable(
name = "user_permission",
joinColumns = #JoinColumn(name = "id"),
inverseJoinColumns = #JoinColumn(name = "permission"))
private List<Permission> permissions;
}
Permission.java
#Data
#Entity
public class Permission {
#Id
private String permission;
private String permissionName;
private String permissionDescription;
}
Generated SQL
select
permission0_.id as id1_2_0_,
permission0_.permission as permissi2_2_0_,
permission1_.permission as permissi1_0_1_,
permission1_.permission_description as permissi2_0_1_,
permission1_.permission_name as permissi3_0_1_
from user_permission permission0_
inner join permission permission1_ on permission0_.permission=permission1_.permission
where permission0_.id=?
Error
ERROR 15056 --- [tp1962586186-25] o.h.engine.jdbc.spi.SqlExceptionHelper : Unknown column 'permission0_.id' in 'field list'
The problem was join columns name. It should have been mapped to the mapping tables FK instead of the parent tables PK. Above example is fixed with this change
#ManyToMany
#JoinTable(
name = "user_permission",
joinColumns = #JoinColumn(name = "user_id"),
inverseJoinColumns = #JoinColumn(name = "permission"))
private List<Permission> permissions;

OneToOne mapping issue for non primary field

I am trying to map a non primary key column between 2 tables using one-to-one mapping through JPA.
The OneToOne is not performing the join on the mentioned column rather it is picking up the Id field.
Below is the table structure:
Person table
id (PK)
name
college
College table
id (PK)
clg_name
location
Location table
id (PK)
loc_name
I need to provide OneToOne mapping between College and Location using the columns location and loc_name respectively. I have tried using the #NaturalId, #MapsId and by providing the reference column name. Still it uses the id field
//person
#Entity
#Table(name = "PERSON", schema = "DETAILS")
#SecondaryTables({
#SecondaryTable(name = "COLLEGE", schema = "DETAILS")
})
class Person{
Person(){
this.college = new College();
}
#Id
#Column(name = "ID", nullable = false)
private Long id;
#Column(name = "NAME", nullable = false)
private String name;
#Column(name = "COLLEGE_NAME", table = "COLLEGE", nullable = false)
private String college;
#OneToOne(cascade = { CascadeType.ALL }, fetch = FetchType.EAGER, mappedBy = "person")
#JoinColumn(name = "ID")
private College college;
//getter setters
}
//college
#Entity
#Table(name = "COLLEGE", schema = "DETAILS")
class College{
College(){
}
#Id
#MapsId
#OneToOne()
#JoinColumn(name = "ID")
private Person person;
#OneToOne(cascade = {CascadeType.ALL}, fetch = FetchType.EAGER, mappedBy = "college")
#JoinColumn(referencedColumnName = "LOC_NAME")
private Location location;
#Column(name = "LOCATION", nullable = false)
private String loc;
//getter setters
}
//location
#Entity
#Table(name = "LOCATION", schema = "DETAILS")
class Location{
Location(){}
#Id
#Column(name = "ID")
private Long collegeId;
#MapsId
#OneToOne()
#JoinColumn(name = "LOC_NAME", referencedColumnName ="LOCATION", nullable = false, unique = true)
private College college;
#Column(name = "LOC_NAME", nullable = false)
private String locName;
//getter setters
}
In the above code, I am facing in OneToOne mapping issue using the location name columns. I am querying the Person object from the JPA repository by querying "from Person p where p.id = :id".
The generated JPA queries in logs for 1to1 mapping appears to be
select from details.college college0_ left outer join details.location location1_ on college0_.id=location1_.locName where college0_.id=?
Error:
Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.dao.InvalidDataAccessResourceUsageException: could not extract ResultSet; SQL [n/a]; nested exception is org.hibernate.exception.SQLGrammarException: could not extract ResultSet] with root cause
Caused by: java.sql.SQLSyntaxErrorException: ORA-01722: invalid number
at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:447)
at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:396)
at oracle.jdbc.driver.T4C8Oall.processError(T4C8Oall.java:951)
at oracle.jdbc.driver.T4CTTIfun.receive(T4CTTIfun.java:513)
If I remove #MapsId from Location then I get below error:
org.hibernate.AnnotationException: A Foreign key refering has the wrong number of column. should be 0

Delete entity from many to many in jpa

I have an Entity
#Entity
#Table(name="user")
public class User {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="id")
private Long id;
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinTable(name = "friends")
private Set<User> friends;
}
Currently I remove a friend by getting first all the friends from database which is not efficient/optimal
#Override
#Transactional
public void deleteFriend(String username, String friend) {
User user = getUser(username);
User other = getUser(friend);
user.getFriends().remove(other);
other.getFriends().remove(user);
}
Since it's not efficient I would like to use this query
public interface UserRepository extends PagingAndSortingRepository<User, Long>{
void removeByUsernameAndFriendsUsername(String username, String otherUsername);
}
And when I try to delete a friend with this query I get an exception
Caused by: org.h2.jdbc.JdbcSQLException: Referential integrity constraint violation: "FK8KCUM44FVPUPYW6F5BACCX25C: PUBLIC.COMMENT FOREIGN KEY(USER_ID) REFERENCES PUBLIC.USER(ID) (3)"; SQL statement:
delete from user where id=? and version=? [23503-196]
at org.h2.message.DbException.getJdbcSQLException(DbException.java:345)
at org.h2.message.DbException.get(DbException.java:179)
at org.h2.message.DbException.get(DbException.java:155)
at org.h2.constraint.ConstraintReferential.checkRow(ConstraintReferential.java:425)
at org.h2.constraint.ConstraintReferential.checkRowRefTable(ConstraintReferential.java:442)
at org.h2.constraint.ConstraintReferential.checkRow(ConstraintReferential.java:317)
at org.h2.table.Table.fireConstraints(Table.java:980)
at org.h2.table.Table.fireAfterRow(Table.java:998)
at org.h2.command.dml.Delete.update(Delete.java:101)
at org.h2.command.CommandContainer.update(CommandContainer.java:101)
at org.h2.command.Command.executeUpdate(Command.java:260)
at org.h2.jdbc.JdbcPreparedStatement.executeUpdateInternal(JdbcPreparedStatement.java:164)
at org.h2.jdbc.JdbcPreparedStatement.executeUpdate(JdbcPreparedStatement.java:150)
at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:204)
... 68 more
Do you know if it's possible to delete it with #NamedQuery or #Query where I don't have to get all the friends first? Something like the query below.
#Query(value = "delete from User U join U.friends friends where U.username = ?1 and friends.username = ?2")
Here's the comment entity
#Entity
#Table(name="comment")
public class Comment {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="id")
private Long id;
#NotNull
#Size(min=1)
#Column(name="text", nullable=false)
private String text;
#NotNull
#ManyToOne(fetch=FetchType.EAGER)
#JoinColumn(name = "user_id", nullable = false)
private User user;
}
Try:
#Modifying
#Query("delete from User U join U.friends friends where U.username = ?1 and friends.username = ?2)
....

ERROR: update or delete on table "tablename" violates foreign key constraint

I'm trying to delete the parent student or parent course and I get this error:
Caused by: org.postgresql.util.PSQLException: ERROR: update or delete on table "student" violates foreign key constraint "fkeyvuofq5vwdylcf78jar3mxol" on table "registration"
RegistrationId class is a composite key used in Registration class. I'm using Spring data jpa and spring boot.
What am I doing wrong? I know that putting cascadetype.all should also remove the children when the parent is deleted but it is giving me an error instead.
#Embeddable
public class RegistrationId implements Serializable {
#JsonIgnoreProperties("notifications")
#OneToOne(cascade=CascadeType.ALL)
#JoinColumn(name = "student_pcn", referencedColumnName="pcn")
private Student student;
#JsonIgnoreProperties({"teachers", "states", "reviews"})
#OneToOne(cascade=CascadeType.ALL)
#JoinColumn(name = "course_code", referencedColumnName="code")
private Course course;
Registration class
#Entity(name = "Registration")
#Table(name = "registration")
public class Registration {
#EmbeddedId
private RegistrationId id;
When you're using a relational DB, you are setting entities with relationships between these entities.
The error that you're getting means that:
You're trying to delete a record that its primary key is functioning as a foreign key in another table, thus you can't delete it.
In order to delete that record, first, delete the record with the foreign key, and then delete the original that you wanted to delete.
I made it work by using hibernate #OnDelete annotation. Some how the JPA.persistence CascadeTypes were not working. They had no effect for whichever I chose.
Just like below. Now I can remove the parent Student or the parent Course and all children(Registrations) are deleted with them.
#Embeddable
public class RegistrationId implements Serializable {
#JsonIgnoreProperties("notifications")
#OnDelete(action = OnDeleteAction.CASCADE)
#OneToOne
#JoinColumn(name = "student_pcn", referencedColumnName="pcn")
private Student student;
#JsonIgnoreProperties({"teachers", "states", "reviews"})
#OnDelete(action = OnDeleteAction.CASCADE)
#OneToOne
#JoinColumn(name = "course_code", referencedColumnName="code")
private Course course;
Foreign keys guarantee that an entry will exist in another table. This is a way of ensuring data integrity. SQL will never allow you to delete this entry while it still deletes in the other table. Either (1) this is letting you know you would have made a grave mistake by deleting this thing which is required or (2) you would like to put in a cascading delete so that not only is this entry deleted but so is what is supposed to be referencing it in the other table. Information on cascading deletes can be found here and written fairly easily (https://www.techonthenet.com/sql_server/foreign_keys/foreign_delete.php). If neither of these two descriptions fits you, evaluate why your foreign key relationship exists in the first place because it probably should not.
Try this method too. I got the answer with this method,This is just a test to remove.
Pay attention to the cascade!
MyUser Entity
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String firstname;
private String lastname;
private String mobile;
#Column(unique = true)
private String email;
private Long date;
private LocalTime localiime;
private LocalTime localiimeend;
#ManyToOne(fetch = FetchType.LAZY,cascade = CascadeType.MERGE)
#JoinColumn(foreignKey = #ForeignKey(name = "role_fk"))
private Role role;
Role Entity
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String description;
#OneToMany(mappedBy = "role", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private List<MyUser> users;
#ManyToOne (fetch = FetchType.LAZY,cascade = CascadeType.MERGE)
#JoinColumn(foreignKey = #ForeignKey(name = "rolecat_fk"))
private rolecat rolecat;
rolecat Entity
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#OneToMany(mappedBy = "rolecat", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private List<Role> roles;

Select username and role name

As the title indicates I'm trying to select both username and role name by using the following query.
select u.username, r.name
from users u, role r
inner join users_roles ur
on ur.user_id = u.id
where username = ?;
However I'm getting the below error
[2017-04-05 21:34:49] [42P01] ERROR: invalid reference to FROM-clause entry for table "u"
[2017-04-05 21:34:49] Hint: There is an entry for table "u", but it cannot be referenced from this part of the query.
[2017-04-05 21:34:49] Position: 79
My user entity is as follows
#Entity(name = "users") // Postgres doesn't like the table name "user"
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String username;
...
#ManyToMany
#JoinTable(
name = "users_roles",
joinColumns = #JoinColumn(
name = "user_id", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(
name = "role_id", referencedColumnName = "id"))
private Collection<Role> roles;
...
And my role entity is as follows
#Entity
public class Role {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String name;
#ManyToMany(mappedBy = "roles")
private Collection<User> users;
...
Any clues about what I'm doing wrong?
SELECT users.username, role.name
FROM users
LEFT OUTER JOIN users_roles
ON users.id = users_roles.user_id
LEFT OUTER JOIN role
ON users_roles.role_id = role.id
WHERE username = ?