Can I use #Where annotation along with #ManytoOne association? - mysql

EER Diagram
I am not an expert in Spring, JPA, Hibernate or MySql.
However I am using all for a web service supporting RESTful calls.
I am building a store management app backend with Spring.
My entities at this point of time are StoreModel, StoreUserModel, StoreUserRoleModel and StoreUserAuthModel.
I have setup bidirectional relationships(OneToMany and ManyToOne) between
StoreModel - StoreUserAuthModel,
StoreUserMode - StoreUserAuthModel and
StoreUserRoleMode - StoreUserAuthModel.
I dont want the foreign key constraint though there are foreign key fields storeid, roleid and userid in StoreUserAuthModel.
Now All the four tables have isdeleted column to implement soft delete.
I am lazy fetching the associations. However I dont want the softdeleted values whenever i query the associations.
I would like to know if I can use #Where annotation along with the #ManyToOne annotation in the StoreUserAuthModel entity?
The issue is different from How to use #Where in Hibernate because my problem is with ManyToOne annotation whereas I have used the where annotation with OneToMany
#Entity
#Table(name = "store")
public class StoreModel {
#NotBlank
private String name;
#NotBlank
private String address;
#NotBlank
private String city;
#NotBlank
private String phone;
#JsonIgnore
#OneToMany(fetch = FetchType.LAZY)
#JoinColumn(name = "storeid", foreignKey = #ForeignKey(name="none", value = ConstraintMode.NO_CONSTRAINT ))
#Where(clause="isdeleted = 0")
private List<StoreUserAuthModel> authList = new ArrayList<StoreUserAuthModel>();
...
}
#Entity
#Table(name = "storerole")
public class StoreRoleModel {
#NotBlank
private String name;
#NotBlank
private Integer rolehierarchy;
#JsonIgnore
#OneToMany(fetch = FetchType.LAZY)
#JoinColumn(name = "roleid", foreignKey = #ForeignKey(name="none", value = ConstraintMode.NO_CONSTRAINT ))
#Where(clause="isdeleted = 0")
private List<StoreUserAuthModel> authList = new ArrayList<StoreUserAuthModel>();
...
}
#Entity
#Table(name = "storeuser")
public class StoreUserModel{
#NotBlank
#Column(unique = true)
private String username;
#Email
#Column(unique = true)
private String useremail;
#JsonIgnore
#OneToMany(fetch = FetchType.LAZY)
#JoinColumn(name = "userid", foreignKey = #ForeignKey(name="none", value = ConstraintMode.NO_CONSTRAINT ))
#Where(clause="isdeleted = 0")
List<StoreUserAuthModel> userAuthList = new ArrayList<StoreUserAuthModel>();
...
}
#Entity
#Table(name = "storeuserauth",
uniqueConstraints = #UniqueConstraint(columnNames = {"storeid", "roleid", "userid"}))
public class StoreUserAuthModel {
#NotNull
Long storeid;
#NotNull
Long roleid;
#NotNull
Long userid;
// Using #where to filter out the soft deleted storeuser
#JsonIgnore
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="userid", foreignKey = #ForeignKey(name="none", value = ConstraintMode.NO_CONSTRAINT ),insertable = false, updatable = false )
#Where(clause="isdeleted = 0")
private StoreUserModel storeuser;
// Using #where to filter out the soft deleted store
#JsonIgnore
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="storeid", foreignKey = #ForeignKey(name="none", value = ConstraintMode.NO_CONSTRAINT ),insertable = false, updatable = false )
#Where(clause="isdeleted = 0")
private StoreModel store;
// Using #where to filter out the soft deleted role
#JsonIgnore
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="roleid", foreignKey = #ForeignKey(name="none", value = ConstraintMode.NO_CONSTRAINT ),insertable = false, updatable = false )
#Where(clause="isdeleted = 0")
private StoreRoleModel role;
...
}
// In the controller, Following code shows how I plan to use
Optional<StoreUserModel> aUser = storeUserRepository.findByUseremailAndIsdeleted(zUserMail), 0);
if(aUser.isPresent()) {
// The user was found!!!
// Testing...
// Getting the User Auth List (that will filter out the soft deleted auths)
List<StoreUserAuthModel> authList = aUser.get().getUserAuthList();
for(StoreUserAuthModel auth :authList) {
StoreModel store = auth.getStore();
// here both soft deleted store as well as normal stores are shown.
// ie where clause on store relation is not working!!
logger.debug("Store is "+store.getName());
}
}
...
Now all the store rows matching the id are in the list.
The expected result should apply where clause too
I turned on logging for hibernate 5.3.9
There is no where clause when it fires the select query

The #Where annotation has no effect on ToOne relationships. But instead of adding #Where to the reference you can use #Where on the Entity:
#Where(clause="isdeleted = 0")
#Entity
#Table(name = "storerole")
public class StoreRoleModel {
That way no deleted entities of StoreRoleModel will be loaded by Hibernate.

Related

JPA/Hibernate - How implement soft delete in a #OneToMany relationship

I'm trying implement soft delete in a #OneToMany relationship in an academic project. I need to mark as "deleted" a child entity in the following scenarios:
When the parent entity is deleted (soft deleted as well).
When the child entity is no longer referenced by his parent.
I've achieved this requirement using the #SqlDelete annotation, and the CascadeType.all / orphanRemoval = true options on the #OneToMany side as follow:
Parent
#Entity
#EntityListeners(AuditingEntityListener.class)
#SQLDelete(sql = "update Discount set deleted = true where id = ?")
#Where(clause = "deleted = false")
public class Discount extends BaseDao {
#Id
#GeneratedValue(generator = "uuid")
#GenericGenerator(name = "uuid", strategy = "uuid2")
private String id;
/// More properties ...
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "discountId", nullable = false)
private List<PetrolStationDiscount> petrolStationDiscounts = new ArrayList<>();
public Discount() {}
// Getters and setters...
public List<PetrolStationDiscount> getPetrolStationDiscounts() {
return petrolStationDiscounts;
}
public void setPetrolStationDiscounts(List<PetrolStationDiscount> petrolStationDiscounts) {
this.petrolStationDiscounts = petrolStationDiscounts;
}
// HashCode & Equals methods
Child Entity
#Entity
#EntityListeners(AuditingEntityListener.class)
#SQLDelete(sql="UPDATE PetrolStationDiscount SET deleted = true WHERE id = ?")
public class PetrolStationDiscount extends BaseDao {
#Id
#GeneratedValue(generator = "uuid")
#GenericGenerator(name = "uuid", strategy = "uuid2")
private String id;
#ManyToOne
#JoinColumn(name = "petrolStationId")
private PetrolStation petrolStation;
#Column(insertable = false, updatable = false)
private String discountId;
// Getters & Setters -- HashCode & Equals methods
BaseDAO
#MappedSuperclass
public abstract class BaseDao {
#CreationTimestamp
#Temporal(TemporalType.TIMESTAMP)
#Column(nullable = false)
private Date createdAt;
#UpdateTimestamp
#Temporal(TemporalType.TIMESTAMP)
#Column(nullable = false)
private Date updatedAt;
#Column(name = "deleted", nullable = false)
private boolean deleted = false;
// Getters and Setters
}
This works fine, but I'm not comfortable mixing the sql statement straight into the entities, so I've tried the approach I found in the following thread:
How to soft delete parent and child together (cascade) using spring boot with jpa
Following the thread, I've created a BaseRepository overriding the CrudRepository methods "delete" and "deleteById", but it doesn't work:
When the parent entity is deleted, only the parent deleted field is saved as true.
When a child entity is no longer referenced by his parent, if orphanRemoval is set to true, the child entity is hard deleted, and if false, nothing happened.
#NoRepositoryBean
public interface BaseRepository<T extends BaseDao> extends CrudRepository<T, Serializable> {
#Override
#Query("update #{#entityName} e set e.deleted = true where e.id = ?1")
#Transactional
#Modifying
void deleteById(Serializable id);
#Override
#Transactional
default void delete(T entity) {
deleteById(entity.getId());
}
¿Someone could help me?
A lot of thanks!

Spring boot, How to perform conditional query on many to many relationship with bridge table?

I have three entity include bridge entity:
Team Entity:
public class Team {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "TEAM_ID")
private Integer id;
#Column(name = "teamname", length = 128, nullable = false, unique = true)
private String teamname;
#Column(name = "delete_date", length = 128, nullable = true)
private Date delete_date;
#Column(name = "description", nullable = true, length = 240)
private String description;
#Column(name = "active", length = 64, nullable = false)
private int active;
#OneToMany(mappedBy = "team", fetch = FetchType.LAZY)
private Set<TeamUsers> team_users = new HashSet<TeamUsers>();
---getter setter constructur
}
User Entity:
#Entity
#Table(name = "tblUsers")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#Column(name = "Username", length = 128, nullable = false, unique = true)
private String username;
#Column(name = "FirstName", nullable = false, length = 45)
private String firstName;
#Column(name = "LastName", nullable = false, length = 45)
private String lastName;
#Column(name = "Password", length = 64, nullable = false)
private String password;
#Column(name = "Email", length = 128, nullable = false, unique = true)
private String email;
#Column(name = "Phone", length = 64, nullable = false, unique = true)
private String phoneNumber;
#OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
private Set<TeamUsers> team_users = new HashSet<TeamUsers>();
---getter setter constructur
}
TeamUsers - Bridge Entity with extra column(active):
#Entity
#Table(name = "team_users")
public class TeamUsers implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#ManyToOne(fetch = FetchType.LAZY, optional = false, cascade = CascadeType.ALL)
#JoinColumn(name = "TEAM_ID")
private Team team;
#ManyToOne(fetch = FetchType.LAZY, optional = false, cascade = CascadeType.ALL)
#JoinColumn(name = "USER_ID")
private User user;
#Column(name = "active")
private Integer active;
---getter setter constructur
}
In the Team repository I have code:
package com.crmbackend.allService.teamService.repo;
import java.util.List;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.PagingAndSortingRepository;
import com.crmbackend.entity.Team;
public interface TeamRepository extends PagingAndSortingRepository<Team, Integer> {
#Query("select t from Team t")
public List<Team> getAllTeamAndDetails();
}
If I call the getAllTeamAndDetails() method in Junit Test, the result is all team informations:
It basically tells me how many team I have, and team users object who belong to which team.
Now, my question is which I want to get all team information and team user information,
but only their active = 1 in the bridge table.
which means if Team User record has active = 0, then this user should not showing in the result.
How this query should be looks like or what is the best approach?
Thanks
This is not possible with the plain JPA/Hibernate or Spring Data JPA tools available. You have to use a DTO for this purpose. I think this is a perfect use case for Blaze-Persistence Entity Views.
I created the library to allow easy mapping between JPA models and custom interface or abstract class defined models, something like Spring Data Projections on steroids. The idea is that you define your target structure(domain model) the way you like and map attributes(getters) via JPQL expressions to the entity model.
A DTO model for your use case could look like the following with Blaze-Persistence Entity-Views:
#EntityView(Team.class)
public interface TeamDto {
#IdMapping
Integer getId();
String getDescription();
#Mapping("team_users[active = 1].user")
Set<UserDto> getUsers();
#EntityView(User.class)
interface UserDto {
#IdMapping
Integer getId();
String getUsername();
}
}
Querying is a matter of applying the entity view to a query, the simplest being just a query by id.
TeamDto a = entityViewManager.find(entityManager, TeamDto.class, id);
The Spring Data integration allows you to use it almost like Spring Data Projections: https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features
Page<TeamDto> findAll(Pageable pageable);
The best part is, it will only fetch the state that is actually necessary!

JPA soft delete with #SqlDelete causes SQL error: Parameter index out of range (2 > number of parameters, which is 1)

I have Basket and BasketItem entities that both extend BaseEntity as below:
#MappedSuperclass
public class BaseEntity {
#Id #GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "deleted", nullable = false)
private Boolean deleted = false;
#Version #Column(nullable = false)
private Long version;
}
#Entity
#Where(clause = "deleted = false")
#SQLDelete(sql = "UPDATE basket SET deleted=true WHERE id=?")
public class Basket extends BaseEntity {
#OneToMany(mappedBy = "basket", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
private List<BasketItem> items = new ArrayList<>();
}
#Entity
#Where(clause = "deleted = false")
#SQLDelete(sql = "UPDATE basketItem SET deleted=true WHERE id=?")
public class BasketItem extends BaseEntity {
#ManyToOne(optional = false)
#JoinColumn(name = "basket_id", nullable = false)
private Basket basket;
}
Now when I want to delete a BasketItem from a Basket, I just call remove(basketItem) on basket items list and save the Basket instance:
#Service
public class DeleteBasketItemUseCase {
#Transactional
public BasketOutput execute(final Long itemId, final Long basketId) {
// loading and checks omitted
basket.getItems().remove(basketItem);
basket = basketRepository.save(basket);
}
}
Deleting basketItem generates MySQL error:
java.sql.SQLException: Parameter index out of range (2 > number of parameters, which is 1)
Generated SQL in log is:
UPDATE
basketItem
SET
deleted=true
WHERE
id = ?
I am using
Spring Boot 2.2.6.RELEASE
MySQL 8.0
Hibernate 5.4.12.Final
mysql-connector-java-8.0.19
I update #SQLDelete and add and version=? AT the end of annotation:
#SQLDelete(sql = "UPDATE basket SET deleted=true WHERE id=? and version=?")
#SQLDelete(sql = "UPDATE basket_item SET deleted=true WHERE id=? and version=?")
Note: Also I've changed database tables names!

JPA mapping for one-to-many collection of shared data, with user specific values

I have a User model that contains a list of achievements
#Table(name = "user")
#Entity
#NamedEntityGraph(name = "User.achievements",
attributeNodes={
#NamedAttributeNode("achievements")
})
#Data
public class User {
#Id
#NotNull
#Column(name = "username")
private String username;
#Column(name = "password")
private String password;
#ElementCollection(fetch = FetchType.LAZY, targetClass = Achievement.class)
private List<Achievement> achievements = new ArrayList<>();
}
Here's the achievement model
#Entity
#Data
#Table(name = "achievement")
public class Achievement {
#Id
#GeneratedValue(generator = "system-uuid")
#GenericGenerator(name = "system-uuid", strategy = "uuid")
private String achievementId;
#Column(name = "title")
private String title;
#Column(name = "description")
private String description;
#Column(name = "achieved", columnDefinition="BOOLEAN DEFAULT false", nullable = false)
private boolean achieved = false;
user_achievements table generated from #ElementCollection mapping, which atm only contains user and achievement foreign keys
I am looking to move the boolean achieved value to the user_achievements table, ideally without having to create a separate model User_Achievements
I am fairly new to using Jpa, but i feel like this scenario is too basic so there must be a straight forward way to do that i cant seem to locate it
#Entity
class UserAchievement {
#EmbeddableId
UserAchievementId id;
#ManyToOne(fetch=LAZY)
#JoinColumn(name="user_username", insertable=false, updatable=false)
User user;
#ManyToOne(fetch=LAZY)
#JoinColumn(name="achivement_achivement_id", insertable=false, updatable=false)
Achivement achivement;
// and other fields
}
class User {
// ...
#OneToMany(mappedBy="user")
List<UserAchievement> userAchievements;
}
and you need to define UserAchievementId

HQL Many to Many JOIN with extra columns

I have a many to many relationship in Hibernate with additional colums so I have an extra java class for the model and another class with the primary key... now in HQL I need a query that retrieve those data but I have problems with Join condition.
This is my First class:
#Entity
#Table(name = "Firsts")
public class First {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
#OneToMany(mappedBy = "primaryKey.second")
private List<FirstsSeconds> seconds = new LinkedList<>();
#Column(name="description")
private String description;
...
}
The Second class:
#Entity
#Table(name="Seconds")
public class Second {
#Id
private String code;
#OneToMany(mappedBy = "primaryKey.first")
private List<FirtsSeconds> firsts = new LinkedList<>();
...
}
And the table manyToMany with additional columns:
#Entity
#Table(name = "firsts_seconds")
#AssociationOverrides({ #AssociationOverride(name = "primaryKey.first", joinColumns = #JoinColumn(name = "id")),
#AssociationOverride(name = "primaryKey.second", joinColumns = #JoinColumn(name = "code")) })
public class FirstsSeconds{
#EmbeddedId
private FirstsSecondsId primaryKey = new FirstsSecondsId();
#Column(name = "extra", nullable = false)
private String extra;
...
}
So the id class:
#Embeddable
public class FirstsSecondsId {
#ManyToOne
private First first;
#ManyToOne
private Second second;
...
}
Finally to get HQL result I create a new class with the field that I want:
public class NewObject
public CargoOrder(String firstDescription, String fsExtra) {
this.firstDescription = firstDescription;
this.fsExtra = fsExtra;
}
...
First of all I want First descrption and extra from FirstsSecond, so this is my query with JOIN fr.seconds as fs:
#Query("SELECT new com.mypackage.NewObject("
+ "fr.description as firstDescription, fs.extra as fsExtra) "
+ "FROM First as fr"
+ "JOIN fr.seconds as fs")
public List<NewObject> findManyToMany();
But I have no results :(... in this case I have to specify the where condition?
#Query("SELECT new com.mypackage.NewObject("
+ "fr.description as firstDescription, fs.extra as fsExtra) "
+ "FROM First as fr"
+ "JOIN fr.seconds as fs WHERE fr.first = fs.primaryKey.first")
public List<NewObject> findManyToMany();
Thats not compile on JOIN fr.seconds as fs WHERE fr.first = fs.primaryKey.first...
Kind regards.
Solved... debugging with spring.jpa.show_sql = true in application-properties I see that there was a bad matching in the join condition, I mapped wrong keys:
In First class it's primaryKey.first (not second):
#OneToMany(mappedBy = "primaryKey.first")
private List<FirstsSeconds> seconds = new LinkedList<>();
And in Second primaryKey.second (not first):
#OneToMany(mappedBy = "primaryKey.second")
private List<FirstsSeconds> firsts = new LinkedList<>();