Projection create new field - spring

I have a class that represents a user date of birth in two separated fields
public class User {
private int yearOfBirth;
private int monthOfBirth;
}
Is it possible to make a projection that exports the user age? I know we can concatenate fields using #Value.

The easiest way to resolve the problem (if you can add code to the domain class) is to add a method in the user class like the one below:
#JsonIgnore
public int getAge() {
return Period.between(
LocalDate.of(dobYear, dobMonth, 1),
LocalDate.now()
).getYears();
}
You can add the #JsonIgnore to block spring from exporting an "age" field when your entity is serialized. After adding that method you can create projection like the one below:
#Projection(name = "userAge ", types = {User.class})
public interface UserAge {
#Value("#{target.getAge()}")
Integer getAge();
}

Something like this, for example:
public class UserAgeDto {
private int yearOfBirth;
private int monthOfBirth;
public UserAgeDto(int yearOfBirth, int monthOfBirth) {
// constructor implementation...
}
public int getAge() {
// age calculation...
}
}
public interface UserRepo extends JpaRepository<User, Long> {
#Query("select new com.example.myapp.dto.UserAgeDto(u.yearOfBirth, u.monthOfBirth) from User u where u = ?")
UserAgeDto getUserAgeDto(User user);
}
Some info

Related

Automapper to Map two List Classes of different structure and also Memberwise explicit Mapping

I have 4 classes namely ClassA, ClassADto, ClassAA(inner class to ClassA) and the final Result class.
ClassAA
{
public int HouseNumber{get;set;}
public string StreetName{get;set;}
public string State{get;set;}
}
ClassA
{
public int Age{get;set;}
public string Name{get;set;}
public ClassAA AObj[get;set;}
}
ClassADto
{
public int Age{get;set;}
public string Name{get;set;}
}
class Result
{
public string StreetName{get;set;}
public int TotalCount{get;set;}
public int TodaysDate{get;set;}
public List<ClassADto> AObjectsList{get;set;}
}
Now my aim is map the 'Result' class with the List of ClassA object to fill it the property 'AObjectsList' as below:
Result data= map mapper.map>(obj);
Also at the same time in automapper i want to use custom function either using 'Resolve' or 'AfterMap' to set properties like 'TodaysDate' to current datetime of system and property 'TotalCount' by counting the number of data.
I tried in many ways using 'CreateMap' and also used 'ForMembers' as from 'classAA' we only need the 'StreetName' but it didn't work. Need some help please.
One time typing approach ;)
public static Result ToResult(this List<ClassA> users)
{
return new Result
{
TotalCount = users.Count,
TodaysDate = DateTime.Today,
AObjectsList = users
.Select(user => new ClassADto
{
Name = user.Name,
Age = user.Age
})
.ToList()
};
}
// Usage
var users = new List<ClassA> { new ClassA(), new ClassA() };
var result = users.ToResult();

QuerySyntaxException with enum

I have a UserAssignmentRole class like this :
#Data
#Entity
public class UserAssignmentRole {
...
#Enumerated(EnumType.STRING)
public Role role;
}
And the Role is enum, it looks like this:
public enum Role{
admin,
member,
pending
}
Now when in my repository I try to query to select all with role of admin, it gives me error:
#Query("select uar from UserAssignmentRole uar where uar.role=Role.admin")
public List<UserAssignmentRole> listAdmin(Long userID, Long assignmentID);
How this can be solved?
Error : org.hibernate.hql.internal.ast.QuerySyntaxException: Invalid path: 'Role.admin'
Full error : https://pastebin.com/tk9r3wDg
It is a strange but intended behaviour of Hibernate since 5.2.x
An enum value is a constant and you're using a non-conventional naming (lowercase)
Take a look at this issue and Vlad Mihalcea's long explanation of the performance penalty.
If you’re using non-conventional Java constants, then you’ll have to set the hibernate.query.conventional_java_constants configuration property to false. This way, Hibernate will fall back to the previous behavior, treating any expression as a possible candidate for a Java constant.
You can try not to write this sql by yourself but with repository create code like this:
#Repository
public interface UserAssignmentRolelRepository extends JpaRepository<UserModel, Long>{
public List<UserAssignmentRole> findByRole(Role role);
}
And then:
#Autowired
UserAssignmentRolelRepository repository ;
public void someMethod(){
List<UserAssignmentRole> userAssignmentRoles = repository.findByRole(Role.admin);
}
UPDATE 1
As it was point out in this answer: non-conventional naming. You can change labels in your enum to uppercase.
public enum Role{
Admin,
Member,
Pending
}
and then:
#Query("select uar from UserAssignmentRole uar where uar.role=com.example.package.Role.Admin")
public List<UserAssignmentRole> listAdmin(Long userID, Long assignmentID);
UPDATE 2
But if you really want to have lowercase in DB.
It requires more code to change. Enum change to:
public enum Role{
Admin("admin"),
Member("member"),
Pending("pending");
private String name;
Role(String name) {
this.name = name;
}
public String getName() { return name; }
public static Role parse(String id) {
Role role = null; // Default
for (Role item : Role.values()) {
if (item.name.equals(id)) {
role = item;
break;
}
}
return role;
}
}
In UserAssignmentRole
// #Enumerated(EnumType.STRING)
#Convert(converter = RoleConverter.class)
private Role role;
And additional class:
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
#Converter(autoApply = true)
public class RoleConverter implements AttributeConverter<Role, String> {
#Override
public String convertToDatabaseColumn(Role role) {
return role.getName();
}
#Override
public Role convertToEntityAttribute(String dbData) {
return Role.parse(dbData);
}
}

MongoRepository custom Id

I have the following MongoDB Repository
public interface TeamRepository extends MongoRepository<Team, TeamId> {
....
}
And the following classes:
public abstract class DbId implements Serializable {
#Id
private final String id;
public DbId(final String id) { this.id = id;}
public String getId() { return id;}
}
public class TeamId extends DbId {
public TeamId(final String id) {
super(id)
}
}
As you can see, I have like a custom id for the repository (I have MongoRepository instead of something like MongoRepository). But, when I am trying to save a Team object, I get an error saying that MongoDB does not know how to generate DBId. Any clue?
MongoDb (or any database) would not know how to generate a string ID without you informing it what the value of the string is.
The default #Id is a string representation of ObjectId, which can be auto-generated by MongoDB. If you are changing the type of string ObjectId to a class, then at least the class needs to define:
** Conversion to string (serialisable), for example:
#Override
public String toString() {
return String.format(
"TeamID[uniqueString=%s]",
myUniqueString);
}
** How to generate the Id.
You can define a method in your TeamRepository i.e. save() to specify how your string can be generated. Alternatively you can check out
https://www.mkyong.com/mongodb/spring-data-mongodb-auto-sequence-id-example/
Where the example specify getNextSequenceId() to generate NumberLong custom id. Hopefully that guides you to your answer.

No composite key property found for type error in Spring JPA2

I have an error in spring JPA
org.springframework.data.mapping.PropertyReferenceException: No property CompanyId found for type CompanyUserDetail!
#Embeddable
public class CompanyUserKey implements Serializable {
public CompanyUserKey() {
}
#Column(name = "company_id")
private UUID companyId;
#Column(name = "user_name")
private String userName;
public UUID getCompanyId() {
return companyId;
}
public void setCompanyId(UUID companyId) {
this.companyId = companyId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
}
#Entity
#Table(name = "company_user_detail")
public class CompanyUserDetail {
#EmbeddedId
CompanyUserKey companyUserkey;
public CompanyUserKey getCompanyUserkey() {
return companyUserkey;
}
public void setCompanyUserkey(CompanyUserKey companyUserkey) {
this.companyUserkey = companyUserkey;
}
}
I am trying to access below method Service layer
#Component
public interface CompanyUserRepository extends JpaRepository<CompanyUserDetail, CompanyUserKey> {
public List<CompanyUserDetail> findByCompanyId(UUID companyId);
}
How can I achieve this ?
Thanks
Since in java model your CompanyUserKey is a property in the CompanyUserDetail class, I believe you should use full path (companyUserkey.companyId) to reach companyId:
public List<CompanyUserDetail> findByCompanyUserkeyCompanyId(UUID companyId);
Also note that you have a naming inconsistency: field in CompanyUserDetail is named companyUserkey instead of companyUserKey.
Assuming you are not using spring-data-jpa's auto generated implementations, your method contents might look something like the following:
FROM CompanyUserDetail c WHERE c.companyUserKey.companyId = :companyId
Now simply provide that query to the EntityManager
entityManager.createQuery( queryString, CompanyUserDetail.class )
.setParameter( "companyId", companyId )
.getResultList();
The key points are:
Query uses a named bind parameter called :companyId (not the leading :).
Parameter values are bound in a secondary step using setParameter method variants.
createQuery uses a second argument to influence type safety so that the return value from getResultList is a List<CompanyUserDetail> just like you requested.
Looking at spring-data-jpa's implementation however, I suspect it could look like this:
public interface CustomerUserRepository
extends JpaRepository<CompanyUserDetail, CompanyUserKey> {
#Query("select c FROM CompanyUserDetail c WHERE c.companyUserKey.companyId = :companyId")
List<CompanyUserDetail> findByCompanyId(#Param("companyId") UUID companyId);
}

Is it possible to avoid typecast on custom #Query?

Imagine that we have an entity:
#Entity
public class Person implements Serializable {
#Id
private String name;
private Long age;
private Boolean isMad;
...
}
And a repository with a trivial (and unnecessary) example for a custom query:
#Repository
public interface PersonRepository extends PagingAndSortingRepository<Info, String> {
#Query("select p.isMad, count(*) from Person p group by p.isMad")
List<Object> aggregateByMadness();
}
Now to parse this List we need to do something like this:
for (Object element : list) {
Object[] result = (Object[]) element;
Boolean isMad = (Boolean) result[0];
Long count = (Long) result[1];
}
which is a pain, can we cast the result of the query directly to List of a POJO?
Yes, you could use the JPQL construction expression:
package com.foo;
public class Madness {
public Madness(boolean isMad, Number count) { /* ...*/ }
}
And in your repository:
#Query("select new com.foo.Madness(p.isMad, count(*)) from Person p group by p.isMad")
List<Madness> aggregateByMadness();

Resources