Can we build Spring Data JPA specification out of a composite key attribute - spring

I am using Spring Data JPA specifications for generic queries to my entities. So far it has worked well. Now the problem happened when I try to use these on composite keys used via embeddedId annotation. So here I need to use a nested property to query which say if the object is Foo and I am trying to write a criteria over id. id1 for example.
#Entity
#Data
public class Foo implements Serializable {
private static final long serialVersionUID = 1L;
/** The key. */
#EmbeddedId
private FooKey id;
}
#Embeddable
#Data
public class FooKey implements Serializable {
private static final long serialVersionUID = 1L;
/** The key. */
private String id1;
private String id2;
}
In the specification I am trying to do
#Override
public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
// get root type
root.get(property).getJavaType()
But this doesn't work for nested attributes like in this case. Is there any way I can be able to build predicates for properties in the composite key.

Example of Equal:
#Override
public Predicate toPredicate(Root<Foo> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
builder.equal(root.get("id").get("id1"),"my val");

You can build a function like this if you want to use multiple levels. In my experience, 2 levels are more than sufficient. But depends on you.
protected Path<Comparable> getPath(Root<EntityOrModel> root) {
Path<Comparable> path;
if (criteria.getKey().contains(".")) {
String[] split = criteria.getKey().split("\\.");
int keyPosition = 0;
path = root.get(split[keyPosition]);
for (String criteriaKeys : split) {
if (keyPosition > 0) {
path = path.get(criteriaKeys);
}
keyPosition++;
}
} else {
path = root.get(criteria.getKey());
}
return path;
}
Then set
#Override
public Predicate toPredicate(Root<EntityOrModel> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
Path<Comparable> path = getPath(root);
// ...
return builder.equal(path, value)
}

Related

JHipster - Insert in the database with the GET method

I have to create an application with Jhipster but i never use it before.
When a user send a GET request to the address http://localhost:8080/api/newmesure/{mac-address}/{value}
I want to insert a new mesure in my database.
First i created 3 entity "Plantes", "Capteurs" and "Mesures" with this format :
Image here : https://i.stack.imgur.com/zJqia.png (I'm not allowed to post)
I activated the JPA Filtering to create a #Query to insert data in my database but i read that was not possible.
In /src/main/java/com/mycompany/myapp/web/rest/MesuresRessources.java :
/**
* REST controller for managing {#link com.mycompany.myapp.domain.Mesures}.
*/
#RestController
#RequestMapping("/api")
public class MesuresResource {
private final Logger log = LoggerFactory.getLogger(MesuresResource.class);
private static final String ENTITY_NAME = "mesures";
#Value("${jhipster.clientApp.name}")
private String applicationName;
private final MesuresService mesuresService;
private final MesuresQueryService mesuresQueryService;
public MesuresResource(MesuresService mesuresService, MesuresQueryService mesuresQueryService) {
this.mesuresService = mesuresService;
this.mesuresQueryService = mesuresQueryService;
}
#GetMapping("/newMesure/{mac}/{value}")
public String newMesure(#PathVariable String mac,#PathVariable int value) {
log.debug("Adresse MAC : "+mac);
log.debug("Valeur : "+value);
#Query("SELECT valeur FROM Mesures WHERE id = 1") //not working
Mesures getValeur(); //not working
return "Mesure ajoutée";
}
}
In /src/main/java/com/mycompany/myapp/domain/Mesures.java :
/**
* A Mesures.
*/
#Entity
#Table(name = "mesures")
public class Mesures implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "valeur")
private Integer valeur;
#ManyToOne(optional = false)
#NotNull
#JsonIgnoreProperties("macs")
private Capteurs mac;
// jhipster-needle-entity-add-field - JHipster will add fields here, do not remove
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Integer getValeur() {
return valeur;
}
public Mesures valeur(Integer valeur) {
this.valeur = valeur;
return this;
}
public void setValeur(Integer valeur) {
this.valeur = valeur;
}
public Capteurs getMac() {
return mac;
}
public Mesures mac(Capteurs capteurs) {
this.mac = capteurs;
return this;
}
public void setMac(Capteurs capteurs) {
this.mac = capteurs;
}
// jhipster-needle-entity-add-getters-setters - JHipster will add getters and setters here, do not remove
#Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof Mesures)) {
return false;
}
return id != null && id.equals(((Mesures) o).id);
}
#Override
public int hashCode() {
return 31;
}
#Override
public String toString() {
return "Mesures{" +
"id=" + getId() +
", valeur=" + getValeur() +
"}";
}
}
Louan
Learning java with JHipster is probably not a wise idea, it uses a very rich technology stack which might lose you unless you invest enough time to learn the basics.
There are many things wrong in your code and approach:
You can't use #Query annotation inside the body of method a of your REST controller, it must be used in your #Repository interface, this code can't compile. See https://www.baeldung.com/spring-data-jpa-query for a quick introduction
JPA filtering is not related to inserting into database
In HTTP/REST, GET method is supposed to be idempotent. For making changes in your database you should use POST or PUT methods. See What is idempotency in HTTP methods?
Your entity naming convention is not consistent: use singular for entity classes because each entity object represents one single instance of Mesure. Here you have Plantes (plural), Capteur (singular) and Mesures (plural). For table names, JHipster uses singular but plural is quite common too because a table holds many rows. Of course, this is just a convention and you or your team may decide to apply another (like a prefix for table names) but the key point is to be consistent.

Is there a way to override UUID binary length from 255 to 16 globally in a spring-boot project?

I want to use binary UUID in a MariaDB database used for a spring-boot project, instead of using varchar uuid. For now, I am able to create, save and search a binary UUID, by override the column length to 16 but I have to manually put the annotation #Column(length=16) on any UUID field.
Is there a way to globally made this modification in the project ?
In other words, is there a way that, for all UUID field in the project, jpa/hibernate create a column "binary(16)" instead of "binary(255)" ?
My problem is that, by default, an UUID is converted into a binary(255) into MariaDB, and with this configuration, JPA Repositories queries are not able to find any data when searching on a UUID field.
To achieve Jpa repositories queries, I have to add the #Column(length=16) on any UUID field.
I have tried to use a "#Converter" but the Convert annotation should not be used to specify conversion of the following: Id attributes, version attributes, relationship attributes etc... And it doesn't work with an uuid relationship field.
I have also tried to use my own custom hibernate type (example here : https://www.maxenglander.com/2017/09/01/optimized-uuid-with-hibernate.html) but the jpa repositories queries don't find anything.
Now i have this :
My abstract entity :
public abstract class GenericEntity {
#Id
#GeneratedValue(generator = "uuid2")
#GenericGenerator(name = "uuid2", strategy = "org.hibernate.id.UUIDGenerator")
#Column(length = 16)
private UUID id;
//...
}
When using an uuid in another object :
public abstract class AnotherEntity extends GenericEntity {
#NotNull
#Column(length = 16)
private UUID owner;
//...
}
I'm looking for a way to override the UUID field generation without putting the "#Column(length = 16)" everywhere.
It would be really great to avoid errors and / or omissions when using the UUID type in others features.
Thanks a lot !
The type descriptor, remapping the binary implementation onto OTHER Hibernate typedef:
import java.sql.Types;
import java.util.UUID;
import org.hibernate.type.descriptor.java.BasicJavaDescriptor;
import org.hibernate.type.descriptor.sql.BinaryTypeDescriptor;
import org.hibernate.type.spi.TypeConfiguration;
public class MariaDBUuidTypeDescriptor extends BinaryTypeDescriptor {
private static final long serialVersionUID = 1L;
public static final MariaDBUuidTypeDescriptor INSTANCE = new MariaDBUuidTypeDescriptor();
public MariaDBUuidTypeDescriptor() {
super();
}
#Override
public int getSqlType() {
return Types.OTHER;
}
#Override
#SuppressWarnings("unchecked")
public BasicJavaDescriptor<UUID> getJdbcRecommendedJavaTypeMapping(TypeConfiguration typeConfiguration) {
return (BasicJavaDescriptor<UUID>) typeConfiguration.getJavaTypeDescriptorRegistry().getDescriptor( UUID.class );
}
}
The type itself, wrapping the descriptor above and binding it to the UUID classdef.
import java.util.UUID;
import org.hibernate.type.AbstractSingleColumnStandardBasicType;
import org.hibernate.type.descriptor.java.UUIDTypeDescriptor;
public class MariaDBUuidType extends AbstractSingleColumnStandardBasicType<UUID> {
private static final long serialVersionUID = 1L;
public static final MariaDBUuidType INSTANCE = new MariaDBUuidType();
public MariaDBUuidType() {
super( MariaDBUuidTypeDescriptor.INSTANCE, UUIDTypeDescriptor.INSTANCE );
}
#Override
public String getName() {
return "mariadb-uuid-binary";
}
#Override
protected boolean registerUnderJavaType() {
return true;
}
}
The modified Hibernate dialect, making use of the type and remapping all its occurrences onto binary(16)
import java.sql.Types;
import org.hibernate.HibernateException;
import org.hibernate.boot.model.TypeContributions;
import org.hibernate.dialect.MariaDB103Dialect;
import org.hibernate.service.ServiceRegistry;
public class MariaDB103UuidAwareDialect extends MariaDB103Dialect {
#Override
public void contributeTypes(TypeContributions typeContributions, ServiceRegistry serviceRegistry) {
super.contributeTypes( typeContributions, serviceRegistry );
registerColumnType( Types.OTHER, "uuid" );
typeContributions.contributeType( MariaDBUuidType.INSTANCE );
}
#Override
public String getTypeName(int code, long length, int precision, int scale) throws HibernateException {
String typeName = super.getTypeName(code, length, precision, scale);
if (Types.OTHER == code && "uuid".equals(typeName)) {
return "binary(16)";
} else {
return typeName;
}
}
}
Please note that this is Hibernale-only implementation, i.e. does not matter if you use it along Spring (Boot) or not.

Spring data jpa Specification: How to filter a parent object by its children object property

My entity classes are following
#Entity
#table
public class User {
#OneToOne
private UserProfile userProfile;
// others
}
#Entity
#Table
public class UserProfile {
#OneToOne
private Country country;
}
#Entity
#Table
public class Country {
#OneToMany
private List<Region> regions;
}
Now I want to get all the user in a particular region. I know the sql but I want to do it by spring data jpa Specification. Following code should not work, because regions is a list and I am trying to match with a single value. How to fetch regions list and compare with single object?
public static Specification<User> userFilterByRegion(String region){
return new Specification<User>() {
#Override
public Predicate toPredicate(Root<User> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
return criteriaBuilder.equal(root.get("userProfile").get("country").get("regions").get("name"), regionalEntity);
}
};
}
Edit: Thanks for the help. Actually I am looking for the equivalent criteria query for the following JPQL
SELECT u FROM User u JOIN FETCH u.userProfile.country.regions ur WHERE ur.name=:<region_name>
Try this. This should work
criteriaBuilder.isMember(regionalEntity, root.get("userProfile").get("country").get("regions"))
You can define the condition for equality by overriding Equals method(also Hashcode) in Region class
Snippet from my code
// string constants make maintenance easier if they are mentioned in several lines
private static final String CONST_CLIENT = "client";
private static final String CONST_CLIENT_TYPE = "clientType";
private static final String CONST_ID = "id";
private static final String CONST_POST_OFFICE = "postOffice";
private static final String CONST_INDEX = "index";
...
#Override
public Predicate toPredicate(Root<Claim> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
List<Predicate> predicates = new ArrayList<Predicate>();
// we get list of clients and compare client's type
predicates.add(cb.equal(root
.<Client>get(CONST_CLIENT)
.<ClientType>get(CONST_CLIENT_TYPE)
.<Long>get(CONST_ID), clientTypeId));
// Set<String> indexes = new HashSet<>();
predicates.add(root
.<PostOffice>get(CONST_POST_OFFICE)
.<String>get(CONST_INDEX).in(indexes));
// more predicates added
return return andTogether(predicates, cb);
}
private Predicate andTogether(List<Predicate> predicates, CriteriaBuilder cb) {
return cb.and(predicates.toArray(new Predicate[0]));
}
If you are sure, that you need only one predicate, usage of List may be an overkill.

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);
}

Generic search with spring-data

I use spring data which I found very interesting but there is a problem I want a generic way to search the field of entity.
I got a entity which have many field
public class Lostcard implements java.io.Serializable {
private Integer id;
private String nom;
private String prenom;
private String cin;
#DateTimeFormat(pattern = "MM/dd/yyyy")
private Date dateDeclaration;
#DateTimeFormat(pattern = "MM/dd/yyyy")
private Date dateDuplicata;
private String annexeAdmin;
[...]
So I want to do this:
public interface LostcardRepository extends JpaRepository<Lostcard, Integer> {
List<Lostcard> findByNom(String nom);
List<Lostcard> findByPrenom(String prenom);
List<Lostcard> findByCin(String cin);
[...]
}
There is not a generic way like findByProperty(String property, Object value) ?
The easiest way in my opinion is to use Specification. You have to make your interface extends also JpaSpecificationExecutor and then you can use your own Specification to execute query.
public interface LostcardRepository extends JpaRepository<Lostcard, Integer>, JpaSpecificationExecutor<Lostcard> {
...
}
Then implement class similar to the one below:
public class PropertySpecifications {
public static Specification<Lostcard> byProperty(final String propertyName, final Object propertyValue) {
return new Specification<Lostcard>() {
#Override
public Predicate toPredicate(Root<Lostcard> candidateRoot, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
return criteriaBuilder.equal(candidateRoot.get(propertyName), propertyValue);
}
};
}
}
Then you can execute query:
lostcardRepository.findAll(Specifications.where(PropertySpecifications.byProperty("property", "value")));
You can declare a query with parameters. You can get more complex with that, but using JpaRepository query methods you can only query for existing entity fields.
#Query("SELECT p FROM Lostcard p WHERE p.yourfield = (:field)")
public Lostcard findByProperty(#Param("field") String property);

Resources