Delete entity from many to many in jpa - 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)
....

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;

#NamedQuery trying to fetch entity and related entity (#OneToOne relation) by related entity property

There are two entities:
#Entity(name = "Account")
#Table(name = "accounts")
#NamedQuery(name = "findAccountByExtId",
query = "SELECT a " +
"FROM Account a " +
"WHERE a.accountDetails.extId = :extId " +
"AND a.deletedAt IS NULL")
public class Account extends DeletableAudit {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue
private UUID id;
#OneToOne(mappedBy = "parent", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
private AccountDetails accountDetails;
and
#Entity(name = "AccountDetails")
#Table(name = "account_details")
public class AccountDetails extends DeletableAudit {
private static final long serialVersionUID = -1L;
#Id
#GeneratedValue
private Integer accDetId;
#OneToOne
#JoinColumn(name = "fk_account")
private Account parent;
#Column
private String extId;
Goal is to return Account and AccountDetials via the external ID. The program won't run due to error:
Failed to create query for method public abstract p.j.o.d.d.Account p.j.o.d.r.AccountRepository.findAccountByExtId(java.lang.String)! No property extId found for type Account!
Already tried different variations of #OneToOne mapping and query, however nothing seems to work.
Second question is whether it is possible to have AccountDetails reference the same ID as Account. I'd like to get rid of accDetId and just store these entities with the same UUID of their parent.

JPA two Entities one Relationship: How do I obtain a Set of an entity that is linked through a relationship?

I have three tables each mapping to one of these entities. The 'assigned' table acts as the relationship between 'users' and 'roles' with a foreign key to each table. How would I map this on my entities so that I can get a Set of EntityRoles from the UserEntity? I can't quite figure out how to make this work. Is this even possible?
#Entity
#Table(name = "users")
public class UserEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name="user_id")
private long id;
#Column(name="user_username")
private String username;
#Column(name="user_password")
private String password;
#Column(name="user_email")
private String email;
//I want to be able to get a set of RoleEntities
#OneToMany(fetch = FetchType.LAZY, mappedBy = "id")
private Set<RoleEntity> roles;
}
#Entity
#Table(name = "assigned")
public class AssignedEntity implements Serializable {
#Id
//#Column(name = "assigned_role")
#ManyToOne(targetEntity = RoleEntity.class, fetch = FetchType.LAZY)
#JoinColumn(name = "fk_role")
private long roleId;
#Id
//#Column(name = "assigned_user")
#ManyToOne(targetEntity = UserEntity.class, fetch = FetchType.LAZY)
#JoinColumn(name = "fk_user")
private long userId;
}
#Entity
#Table(name = "roles")
public class RoleEntity implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name="role_id")
#OneToOne(fetch = FetchType.LAZY, mappedBy="roleId")
private long id;
#Column(name="role_name")
private String name;
}
You are using an incorrect/inconvenient mapping. Always keep things as simply as possible.
#Entity
#Table(name = "users")
public class User {
#Id
#GeneratedValue
private Long id;
#ManyToMany(fetch = FetchType.LAZY)
private List<Role> roles;
}
#Entity
#Table(name = "roles")
public class Role {
#Id
private Long id;
#Column
private String name;
}
A persistent provider will create a (valid) join table for you. You can specify the name of the join table using #JoinTable annotation. Also you will need to think about auto generation values of id for the Role entity: the roles table is something like a reference data table. So, probably, you will need to hardcode the id values.
To get user roles (in the persistent context):
user.getRoles()

Using the Criteria API and Metamodel API to Join two Middle table always cross join

This is a RBAC module,There is three basic table user,role and permission and middle mapping table user_role and role_permission.
#Entity
#Table(name = "USER")
public class User implements Serializable {
#Id
private String userId;
...
}
#Entity
#Table(name = "ROLE")
public class Role implements Serializable {
#Id
private String roleId;
...
}
#Entity
#Table(name = "PERMISSION")
public class Permission implements Serializable {
#Id
private String permissionId;
...
}
#Entity
#Table(name = "USER_ROLE")
public class UserRole implements Serializable {
#Id
#GenericGenerator(name = "uuidGenerator", strategy = "uuid")
#GeneratedValue(generator = "uuidGenerator")
#Column(name = "ID")
private String id;
#ManyToOne
#JoinColumn(name = "USERID")
private User user;
#ManyToOne
#JoinColumn(name = "ROLEID")
private Role role;
...
}
#Entity
#Table(name = "ROLE_PERMISSION")
public class RolePermission implements Serializable {
#Id
private String id;
#ManyToOne
#JoinColumn(name = "PERMISSIONID")
private Permission permission;
#ManyToOne
#JoinColumn(name = "ROLEID")
private Role role;
...
}
and now i want to find all permission by user.id, SQL express like this:
select rp.* from Role_Permission rp,User_Role ur where ur.roleId = rp.roleId and ur.userId = :id
but by Criteria API:
public Predicate toPredicate(Root<RolePermission> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
List<Predicate> predicates = new ArrayList<Predicate>();
if (StringUtil.isNotEmpty(userId)) {
final Root<UserRole> userRoleRoot = criteriaQuery.from(UserRole.class);
Join<RolePermission,UserRole> join = root.join("role", JoinType.INNER);
Predicate predicate = criteriaBuilder.equal(join.get("roleId"), root.get("role").get("roleId"));
predicate = criteriaBuilder.and(predicate,criteriaBuilder.equal(userRoleRoot.<UserRole>get("user").get("id"), userId));
predicates.add(predicate);
}
return criteriaBuilder.and(predicates.toArray(new Predicate[]{}));
}
and query build result is:
select count(rolepermis0_.id) as col_0_0_ from role_permission rolepermis0_ inner join role role2_ on rolepermis0_.roleid=role2_.roleid cross join user_role userrole1_ where role2_.roleid=rolepermis0_.roleid and userrole1_.userid=?
but why here role_permission cross join user_role, how role_permission join user_role by criteria API ?
thanks a lot.
final Subquery<UserRole> userRoleSubquery = criteriaQuery.subquery(UserRole.class);
final Root<UserRole> userRole = userRoleSubquery.from(UserRole.class);
userRoleSubquery.select(userRole.<UserRole>get("id"));
userRoleSubquery.where(criteriaBuilder.equal(root.get("role").get("roleId"), userRole.get("role").get("roleId")), criteriaBuilder.equal(userRole.get("user").get("id"), userId));
Predicate predicate = criteriaBuilder.exists(userRoleSubquery);
predicates.add(predicate);

EntityExistsException When Attempting Bulk Delete With OpenJPA

I have three classes: Location, MTFCC, and BorderPoint.
Location has a unidirectional #ManyToOne relationship with MTFCC, which is intended only as a Lookup table. No cascading is defined.
Location also has a bidirectional #ManyToOne/#OneToMany with BorderPoint. Since I want all associated BorderPoint objects to delete when I delete a Location, I set cascadetype.ALL on the Location side of the relationship.
Unfortunately, an EntityExistsException is being thrown when I attempt to delete a location:
org.apache.openjpa.persistence.EntityExistsException: Cannot delete or update
a parent row: a foreign key constraint fails (`mapmaker`.`BORDERPOINT`,
CONSTRAINT `BORDERPOINT_ibfk_1` FOREIGN KEY (`LOCATIONID`) REFERENCES `LOCATION`
(`LOCATIONID`)) {prepstmnt 21576566 DELETE t0, t1 FROM LOCATION t0 INNER JOIN
MTFCC t1 ON t0.MTFCCID = t1.MTFCCID WHERE (t0.STATEFP = ? AND t1.MTFCCCODE = ?)
[params=?, ?]} [code=1451, state=23000]
[ERROR] FailedObject: DELETE t0, t1 FROM LOCATION t0 INNER JOIN MTFCC t1 ON
t0.MTFCCID = t1.MTFCCID WHERE (t0.STATEFP = ? AND t1.MTFCCCODE = ?)
[java.lang.String]
It looks like it's attempting to delete the associated MTFCC object which I do NOT want to happen. I do, however, want the associated BorderPoint objects to be deleted.
Here is the code (chopped down a bit):
#SuppressWarnings("unused")
#Entity
#Table(name="LOCATION")
#DetachedState(enabled=true)
public class Location implements Serializable, IsSerializable, Cloneable {
private Long id;
private String stateGeoId;
private MTFCC mtfcc;
private List<BorderPoint> borderPointList;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="LOCATIONID")
public Long getId() {
return id;
}
#ManyToOne
#JoinColumn(name="MTFCCID")
public MTFCC getMtfcc() {
return mtfcc;
}
#OneToMany(cascade = CascadeType.ALL, mappedBy = "location", fetch = FetchType.EAGER)
public List<BorderPoint> getBorderPointList() {
return borderPointList;
}
}
#Entity
#Table(name = "BORDERPOINT")
#DetachedState(enabled = true)
public class BorderPoint implements Serializable, IsSerializable {
private Long id;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="BORDERID")
public Long getId() {
return id;
}
#ManyToOne(targetEntity = Location.class)
#JoinColumn(name="LOCATIONID")
public Location getLocation() {
return location;
}
}
#Entity
#Table(name = "MTFCC")
public class MTFCC implements Serializable, IsSerializable {
private Long id;
private String mtfccCode;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "MTFCCID")
public Long getId() {
return id;
}
// etc
}
And, for good measure, here is the deletion code:
#Override
#Transactional
public int removeByStateGeoIdAndMtfcc(String stateGeoId, String mtfccCode) throws RepositoryException {
EntityManager em = entityManagerFactory.createEntityManager();
String jpaQuery = "DELETE FROM Location L where L.stateFP = ?1 AND L.mtfcc.mtfccCode = ?2";
int affectedRows = 0;
Query query = em.createQuery(jpaQuery).setParameter(1, stateGeoId).setParameter(2, mtfccCode);
try {
em.getTransaction().begin();
affectedRows = query.executeUpdate();
em.getTransaction().commit();
} catch (Exception e) {
//log.debug("Exception: ", e);
throw new RepositoryException(e);
}
em.close();
return affectedRows;
}
Hopefully I copied all relevant parts... can anyone assist?
You aren't reading the error message correctly. It says that the deletion is forbidden because of the foreign key constraint between BorderPoint and Location.
The cascade delete would work if you used em.remove(location) to delete your Location. Using a delete query like you're doing won't automagically delete the BorderPoints before deleting the location.
Either load them and remove them using em.remove, or execute other delete queries before to delete the BorderPoints.