JPA: Pass default parameter into findAll() method - java

I have several entities and jpa repositories to them. It looks like:
Event:
public class Event{
#Column
private String name;
#Column
private String description;
}
Place:
public class Place{
#Column
private String name;
#Column
private String description;
#Column
private Double lon;
#Column
private Double lat;
}
And repositories to them:
public interface EventRepository extends JpaRepository<Event, String>, JpaSpecificationExecutor<Event> {
}
public interface PlaceRepository extends JpaRepository<Place, String>, JpaSpecificationExecutor<Place> {
}
It work well. But then I added one else field in each entity calls tenantId
Event:
public class Event{
#Column
private String tenantId;
#Column
private String name;
#Column
private String description;
}
Place:
public class Place{
#Column
private String tenantId;
#Column
private String name;
#Column
private String description;
#Column
private Double lon;
#Column
private Double lat;
}
But all my service works with method findAll(). So, the question is:
How can I get from "old" method findAll() entities only with tenantId = "1" or "2", doesnt matter? It should be work like findAllByTenantId(String tenantId) but it should be 'findAll()'. Can I inject somehow into 'findAll()' tenantId params?
Thx.

Try to override the implementation :
public interface EventRepository extends JpaRepository<Event, String>, JpaSpecificationExecutor<Event> {
List<Event> findAll(String tenantId); // findAllByTenantId
}
JPA criteria API translates into the following query:
select e from Event e where e.tenantId = ?1
And
public interface PlaceRepository extends JpaRepository<Place, String>, JpaSpecificationExecutor<Place> {
List<Place> findAll(String tenantId); // findAllByTenantId
}
JPA criteria API translates into the following query:
select p from Place p where p.tenantId = ?1

Related

JPA Repository: GetMapping with 2 Params but both params are optional to key in

Based on my question above, I have a table named User. I want to display all the data based on two parameters, status and createdBy. For example, if the user key is in both params, it will show based on both params. If the user just want to key in status = 1 and the createdBy remains empty, the data will show only with status = 1. The same goes for when the user only wants to key in createdBy. If the user does not key in both param, it will display all data.
Now. I only can do this if the user key in both param. Below is my code:
User.java
#Table(name = "idr_user")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String email;
private int status;
#Column(name="factory_id", nullable = true)
private int factoryId;
#Column(name="created_by", nullable = true)
private Integer createdBy;
#Column(name="role_id", nullable = true)
private Integer roleId;
}
UserResponseDto.java
public class UserResponseDto {
private int id;
private String name;
private String email;
private Integer factoryId;
private Integer status;
private Integer createdBy;
private Integer roleId;
}
UserRepository
#Repository
public interface UserRepository extends JpaRepository<User, Long> {
public List<User> findAllUserByStatusAndCreatedBy(Optional<Integer> status, Optional<Integer> createdBy);
}
UserController
public class UserController {
#Autowired
private UserService userService;
#Autowired
private ModelMapper modelMapper;
#GetMapping(path="user/statusAndCreatedBy/{status}/{createdBy}")
public #ResponseBody Iterable<UserResponseDto> getUserByStatusAndCreatedBy(#PathVariable Optional<Integer> status, #PathVariable Optional<Integer> createdBy) {
return userService.getUserByStatusAndCreatedBy(status, createdBy).stream().map(user -> modelMapper.map(user, UserResponseDto.class)).collect(Collectors.toList());
}
}
UserService.java
public class UserService {
#Autowired
private UserRepository userRepository;
public List<User> getUserByStatusAndCreatedBy(Optional<Integer> status, Optional<Integer> createdBy) {
return userRepository.findAllUserByStatusAndCreatedBy(status, createdBy);
}
}
Hence, anyone can help? Im stuck at this. Thank you
You can use JPA specifications for optional parameter filtering
Example:
userRepository.findAll(isStatus(status).and(isCreatedBy(createdBy)));
Here, create isStatus() and isCreatedBy() function using specification.
Read here for how to write specification : https://www.stackhawk.com/blog/using-jpa-specifications-with-kotlin/

MapStruct mapping on objects of type List

I am trying to use MapStruct for a structure similar to the following:
#Data
public class ClassAEntity {
private int id;
private String name;
private String numT;
private List<ClassBEntity) bs;
}
#Data
public class ClassBEntity {
private int id;
private String name;
private String numT;
private List<Other> oc;
}
#Data
public class ClassA {
private int id;
private String name;
private List<ClassB) bs;
}
#Data
public class ClassB {
private int id;
private String name;
private List<Other> oc;
}
In the interface I have added the following mapping:
ClassAEntity map(ClassA classA, String numT)
I get a warning because it can't map numT to classBEntity.numT and I can't add it with #Mapping in the following way:
#Mapping(source = "numT", target = "bs[].numT")
On the other hand I need to ignore the parameter oc of classBEntity because "Other" object contains classAEntity and forms a cyclic object. (because I use oneToMany JPA). I have tried the following:
#Mapping(target = "bs[].oc", ignore = true)
Thank you for your help
MapStruct does not support defining nested mappings for collections. You will have to define more explicit methods.
For example to map numT into bs[].numT and ignore bs[].oc you'll need to do something like:
#Mapper
public MyMapper {
default ClassAEntity map(ClassA classA, String numT) {
return map(classA, numT, numT);
}
ClassAEntity map(ClassA classA, String numT, #Context String numT);
#AfterMapping
default void setNumTOnClassBEntity(#MappingTarget ClassBEntity classB, #Context String numT) {
classB.setNumT(numT);
}
#Mapping(target = "oc", ignore = "true")
ClassBEntity map(ClassB classB);
}

SQL aggregation GROUP BY and COUNT in Spring JPA

I have an SQL table:
#Table(name = "population_table")
public class Population {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String country;
private String state;
private String area;
private String population;
}
I want to get a count, grouping by country and state with the output class being List of Count:
private static class Count {
private String country;
private String state;
private long count;
}
I know the query is
SELECT country, state, Count(*)
FROM population_table
GROUP BY country, state
But I want to do this using JPA Specification. How can I achieve this using JPA Specification in spring boot?
You could achieve this by using Spring Data JPA Projections in Spring Data JPA.
Create a custom Repository method like
#Repository
public interface PopulationRepository extends JpaRepository<Population, Long> {
#Query("select new com.example.Count(country, state, count(p) )
from Population p
group by p.country, p.state")
public List<Count> getCountByCountryAndState();
}
Also you must define the specific constructor in Count class which will handle this projection
private static class Count {
private String country;
private String state;
private long count;
//This constructor will be used by Spring Data JPA
//for creating this class instances as per result set
public Count(String country,String state, long count){
this.country = country;
this.state = state;
this.count = count;
}
}
You can use JpaRepository interface.
Example:
#Repository
public interface PopulationRepository extends JpaRepository<Population, Long> {
public int countAllByCountryAndState(String countryName, String stateName);
}
And in your service:
#Service
#Transactional
public class PopulationService {
#Autowired
private PopulationRepository populationRepository;
public int countPopulationByCountryAndState(String countryName, String stateName) {
return populationRepository.countAllByCountryAndState(countryName, stateName);
}
}
Sorry, I made mistake it can be simpler. I edited my code.

Why filter for Spring Data JPA Specification doesn't work?

I try select data from the table by a filter with Spring Data JPA Specification I think what my implementation is correct, But it doesn't work. Help me please understand my mistake and fix my example.
I have very strange SQL query in log :
select phone0_.id as id1_0_, phone0_.note as note2_0_, phone0_.number as number3_0_, phone0_.operator_login as operator4_0_, phone0_.operator_pass as operator5_0_, phone0_.operator_name as operator6_0_, phone0_.operator_url as operator7_0_, phone0_.reg_date as reg_date8_0_, phone0_.status as status9_0_ from phone phone0_ where 0=1 limit ?
In the end: where 0=1 it's crash my mind. Where did that come from?
Here I fill CriteriaBuilder if filter field not null. I expect to get correctly built Specification object and send it to findAll(Specifications.where(specification), Pageable p) method. But something incorrect.
My repo and specification impl:
public interface PhoneRepository extends CrudRepository<Phone, Integer>, JpaRepository<Phone, Integer>, JpaSpecificationExecutor<Phone> {
class PhoneSpecification implements Specification<Phone> {
private final #NonNull PhoneService.PhoneFilter filter;
public PhoneSpecification(#NonNull PhoneService.PhoneFilter filter) {
this.filter = filter;
}
#Override
public Predicate toPredicate(Root<Phone> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
Predicate predicate = cb.disjunction();
if (nonNull(filter.getId())) {
cb.disjunction().getExpressions().add(cb.equal(root.get("id"), filter.getId()));
}
if (nonNull(filter.getNote())) {
cb.disjunction().getExpressions().add(cb.like(root.get("note"), filter.getNote()));
}
if (nonNull(filter.getNumber())) {
cb.disjunction().getExpressions().add(cb.like(root.get("number"), filter.getNumber()));
}
if (nonNull(filter.getStatus())) {
cb.disjunction().getExpressions().add(cb.like(root.get("status"), filter.getStatus()));
}
if (nonNull(filter.getOpName())) {
cb.disjunction().getExpressions().add(cb.like(root.get("operatorName"), filter.getOpName()));
}
if (nonNull(filter.getOpLogin())) {
cb.disjunction().getExpressions().add(cb.like(root.get("operatorAccLogin"), filter.getOpLogin()));
}
if (nonNull(filter.getOpPassword())) {
cb.disjunction().getExpressions().add(cb.like(root.get("operatorAccPassword"), filter.getOpPassword()));
}
if (nonNull(filter.getRegFrom()) && nonNull(filter.getRegTo())) {
cb.disjunction().getExpressions().add(cb.between(root.get("regDate"), filter.getRegFrom(), filter.getRegTo()));
}
return predicate;
}
}
}
This is service level:
#Service
public class PhoneService {
#Autowired
private PhoneRepository phoneRepository;
public Phone get(int id) {
Phone phone = phoneRepository.findOne(id);
return nonNull(phone) ? phone : new Phone();
}
public Page<Phone> list(#NonNull PhoneFilter filter) {
PhoneSpecification specification = new PhoneSpecification(filter);
return phoneRepository.findAll(Specifications.where(specification), filter.getPageable());
}
#Data
public static class PhoneFilter {
private Pageable pageable;
private Integer id;
private Timestamp regFrom;
private Timestamp regTo;
private String number;
private String opLogin;
private String opPassword;
private String opName;
private String status;
private String note;
}
}
And entity
#Entity
#Data
#NoArgsConstructor
#AllArgsConstructor
#Table(name = "phone")
#ToString(exclude = {"accounts"})
#EqualsAndHashCode(exclude = {"accounts"})
public class Phone {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#OneToMany(mappedBy = "phone", cascade = CascadeType.DETACH)
private Collection<SocialAccount> accounts;
#Column(name = "reg_date")
private Timestamp regDate;
#Column(name = "number")
private String number;
#Column(name = "operator_url")
private String operatorUrl;
#Column(name = "operator_login")
private String operatorAccLogin;
#Column(name = "operator_pass")
private String operatorAccPassword;
#Column(name = "operator_name")
private String operatorName;
#Column(name = "status")
private String status;
#Column(name = "note")
private String note;
}
I find the mistake.
Method CriteriaBuilder.disjunction() this is factory and each time when I call him I got new Predicate object.
This implementation CriteriaBuilderImpl:
public Predicate disjunction() {
return new CompoundPredicate(this, BooleanOperator.OR);
}
Be careful with it.

multiple language support data from db

My application has entities with nameEn and nameDe for english and german. But only english being used now. Since there are so many entities available, I wanted to have a generic class which can return the selected language entries,but for multiple entries my approach didn't work.
#Entity
#Table(name="employee")
public class Employee implements java.io.Serializable {
// Fields
private Integer id;
private String nameEn;
private String nameDe;
//Getter, Setter Methods
}
#Entity
#Table(name="address")
public class Address implements
java.io.Serializable {
private String descriptionEn;
private String descriptionDe;
}
public interface ILabelText {
String getNameEn();
String getNameDe();
String getDescriptionEn();
String getDescriptionDe();
}
public abstract class LabelText implements ILabelText, Serializable {
private static final long serialVersionUID = 1L;
protected String descriptionEn;
protected String descriptionDe;
private Logger log = Logger.getLogger(LabelText.class);
String language = FacesContext.getCurrentInstance().getViewRoot().getLocale().getLanguage();
public String getDescription() {
log.info("Language Selected is " + language);
if (language.equals("De")) {
return getDescriptionDe();
} else {
return getDescriptionEn();
}
}
public String getName() {
log.info("Language Selected is " + language);
if (language.equals("De")) {
return getNameDe();
} else {
return getNameEn();
}
}
}
//In Xhtml, based on selected locale, display value accordingly
<h:outputText value="#{emp.getName()}" />
<h:outputText value="#{add.getDescription()}" />
You can create an entity Lang like this
#Entity
public class Lang implements Serializable
{
#Id #GeneratedValue(strategy=GenerationType.IDENTITY)
private long id;
#NotNull
private String key;
#NotNull
private String translation;
}
and use it in your Address like this
#OneToMany(cascade = CascadeType.ALL, fetch=FetchType.EAGER)
#MapKey(name = "key")
protected Map<String, Lang> name;
Then you are able to access the correct language in JSF:
<h:outputText value="#{emp.name[userLocale].translation}" />
The expression userLocale should be resolved to your language key (en, de, ...) or can be hardcoded e.g. #{emp.name['en'].translation}.
Is more easy you create a table with translations:
e.g:
People -> All of your persons
PersonTranslations
People | id
PersonTranslations | locale; person_id;
then on your Person class you set the language for all attributes on predicate
Person.description (this will search on PersonTranslation using a person_id key, and a locale)
some like that PersonTranslation.find(1, 'en');

Categories

Resources