Use enumerations in JPA Query - java

I'm trying to use JPA to update an entity property which is an enumeration in my domain model, and I can't make it work.
I have the following entities:
public class A {
#Id
#Column(name = "id")
private Long id;
#ManyToOne
#JoinColumn(name = "status")
private Status status;
}
public class Status {
#Id
#Enumerated(EnumType.STRING)
#Column(name = "id")
private StatusId id;
public enum StatusId {
B, C, D, E, F
}
}
and this JPA repository:
public interface ARepository extends JpaRepository<A, Long> {
#Modifying
#Transactional
#Query("UPDATE A SET status = ?2 WHERE id = ?1")
void updateStatus(Long id, Status.StatusId status);
}
Executing this method throws the following error:
Caused by: java.lang.IllegalArgumentException: Parameter value [B] did not match expected type [com.***.Status (n/a)]
Any ideas on how to solve this?

The error says:
Parameter value [B] did not match expected type [com.***.Status
and in your query you are effectively trying to assign an object of type Status.StatusId to a field of type Status
You should try to change the query to something like this:
UPDATE A SET status.id = ?2 WHERE id = ?1

Related

I'm receiving one object using findAllBy in springBoot

I'm trying to fetch all rows that have the same patient_id, so I'm doing findAllByPatientId. But I'm always receiving one object in the Listinstead of all the rows.
#Entity
#Getter
#Setter
public class MedicalHistory extends BaseEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#ManyToOne
#JoinColumn(name = "operator_id")
private MedicalOperator medicalOperatorId;
#ManyToOne
#JoinColumn(name = "illness_id")
private Illness illnessId;
#ManyToOne
#JoinColumn(name= "patientId")
private Patient patientId;
}
public List<MedicalHistory> getPatientMedicalRecords(PatientDto patientDto) {
Optional<Patient> getPatient = patientRepository.findByNin(patientDto.getNin());
Long patientId = getPatient.get().getPatientId();
return medicalHistoryRepository.findAllByPatientId(patientId);
}
I want to receive multiple rows using the patient_id but instead, I'm always getting one !!.
I tried native query and hibernate but nothing is working.
public interface MedicalHistoryRepository extends JpaRepository<MedicalHistory, Long> {
// List<MedicalHistory> findAllByPatientId(Long id);
ArrayList<MedicalHistory> findMedicalHistoriesByPatientId(Long id);
#Query(value = "SELECT * FROM medical_history WHERE patient_id = id",nativeQuery = true)
List<MedicalHistory> findAllByPatientId(Long id);
}
Now you are requesting "give me medical_history where id = patient_id" and getting only one result row.
You need to add a colon to the query to set a parameter to fix a result
value = "SELECT * FROM medical_history WHERE patient_id = :id"
Look for JPQL, it's java persistancy query language and spring is automatically turning your #query into it. Spring is also supporting spEL you can also have a look to it here : https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.query.spel-expressions where you can see than you can grab your parameters ever with ?number or with :name or putting #Param("name") into your parameters definition. As said before there is multiple ways to receive a parameter in you query but certainly not like you previously did.
That's why you don't receive anything.

Parameter value [1] did not match expected type [java.lang.Integer (n/a)]

I have entity and rest controller, when I make a request to my controller it throws this exception:
java.lang.IllegalArgumentException: Parameter value [1] did not match expected type [java.lang.Integer (n/a)]
My controller:
#GetMapping("/createCharacter")
public Character createCharacters(#RequestParam("userId") Integer userId, #RequestParam("mapId") long mapId) {
return createCharactersService.createCharacters(userId, mapId);
}
My entity has int type id:
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
For those who find this issue based on the error:
java.lang.IllegalArgumentException: Parameter value [Type#abdc80fc] did not match expected type [Type (n/a)]
You might be using JPA like this:
#Repository
public interface OtherRepository extends JpaRepository<Other, Long> {
List<Other> findAllByType(final Type type);
In that case, please make use of the id of Type (thus: findAllByTypeId).
Since the Id is a uuid you must keep it as a string in the entity.
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private String id;
Therefore you are bound to use it as a String in Service and in Controller.
#GetMapping("/createCharacter")
public Character createCharacters(#RequestParam("userId") String userId, #RequestParam("mapId") long mapId) {
return createCharactersService.createCharacters(userId, mapId);
}
in my case I have used DTOs so
Previously my #query was like this
#query("SELECT ... WHERE ucs.district = ?1")
Then I changed #query like this
#query("SELECT ... WHERE ucs.district.id = ?1")
If you are using JPA with #Query for example :
#Query("SELECT r FROM Employee r WHERE r.empId = ?1)
Employee getMyEmployee(long id);
OR
#Query("SELECT r FROM Employee r WHERE r.empId = :id)
Employee getMyEmployee(#Param("id") long id);
Make sure the passing parameter is long value and empId also a long value. If you execute the method
classObj.getMyEmployee(someIntVariable)
by passing int value it could cause the above error.

returning a list of instances by the foreign key

I have two models, Owner and Contract. A contract has an instance of an owner, owner does not have a list of contracts. I'm trying to query my list of contracts, to return a list filtered by owner, ie, a list of contracts by owner.
I had tried to follow previous examples and use Criteria to write a custom query, but, following suggestions I've checked the docks and tried to use named queries instead, however, I'm still really struggling.
There was an unexpected error (type=Internal Server Error, status=500).
Named parameter not bound : ownerId; nested exception is org.hibernate.QueryException: Named parameter not bound : ownerId
My models look like this:
#Entity
#Table(name="Contracts")
#NamedQueries({
#NamedQuery(
name = "Contract.allContractsByOwner",
query = "SELECT c FROM Contract c WHERE c.owner.id LIKE :ownerId"
)
})
public class Contract {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column
private Long id;
#ManyToOne
private Owner owner;
#Column
private double price;
#Column
private String deliverDate;
public Contract(Owner owner, double price, String deliverDate) {
this.id = id;
this.owner = owner;
this.price = price;
this.deliverDate = deliverDate;
}
and
#Entity
#Table(name="Owners")
public class Owner {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column
private Long id;
#Column
private String name;
public Owner(String name){
this.name = name;
}
my contractRepoImpl
#Service
public class ContractRepositoryImpl implements ContractRepositoryCustom {
ContractRepository contractRepository;
#Autowired
EntityManager entityManager;
public List allContractsByOwner(Long ownerId) {
List contracts = entityManager.createQuery(
"SELECT c FROM Contract c WHERE c.owner.id LIKE :ownerId", Contract.class)
.getResultList();
return contracts;
}
}
which I name in my ContractRepo and ContractRepoCustom files, and then in my controller I map to it like so. But, when I query it in my browser I get the error in my terminal.
#GetMapping(value="/owners/{ownerId}/contracts")
public List allContractsByOwner(#PathVariable("ownerId") Long ownerId){
return contractRepository.allContractsByOwner(ownerId);
}
I appreciate this is probably beginners mistakes, I am trying to follow docs but get a bit stuck with syntax & where annotations need to go.
Thanks JB Nizet, got there in the end
I added parameters to my contractRepoImpl
#Service
public class ContractRepositoryImpl implements ContractRepositoryCustom {
ContractRepository contractRepository;
#Autowired
EntityManager entityManager;
public List allContractsByOwner(Long id) {
List contracts = entityManager.createQuery(
"SELECT c FROM Contract c WHERE c.owner.id = :ownerId", Contract.class)
.setParameter("ownerId", id)
.getResultList();
return contracts;
}
}
that then produced a SQL error, which I fixed by changing my #NamedQuery from 'LIKE' to '=' in my Contract class...
#NamedQueries({
#NamedQuery(
name = "Contract.allContractsByOwner",
query = "SELECT c FROM Contract c WHERE c.owner.id = :ownerId"
)
})

What are some ways to pass an Enum value in a pure JPA #Query?

Let's say I want to set a record's column value as "RESOLVED". However, I want to have this value to picked up from an Enum. So the query stays the same regardless if somebody decides the "RESOLVED" to change to "RESOLVE" or something like that. In other words, the developer would not need to edit the JPA query with the new Enum value. Just wanted to know what are some ways to go about it, or if JPA doesn't allow that. I know queries are static but earlier I was able to pass an object inside a query to store the query result fields in it. So I wondered if one could do the same with Enums too.
This is my current strategy: To pass the "RESOLVE" as a #Param "status". However, I'd still like to know how to pass Enums as this feels like a work-around more than answering the question itself.
public interface UpdateStatusStandardDAO extends JpaRepository<DBTableName, Long>
{
#Transactional
#Modifying(clearAutomatically = true)
#Query("UPDATE"
+ " TableName tn"
+ " SET"
+ " tn.status =:status"
+ " WHERE"
+ " tn.field1 =:field1"
+ " AND tn.field2 =:field2"
+ " AND tn.field3 =:field3")
int updateStatus(#Param("status") String status, #Param("field1") Long field1,
#Param("field2") String field2, #Param("field3") String field3);
}
What I know:
1. #Transactional: for update/delete ops. Otherwise, I got org.springframework.dao.InvalidDataAccessApiUsageException.
2. #Modifying(clearAutomatically = true): Because the entity manager does not flush change automagically by default.
For simplicity, we consider field1 as primary key. It's type is Long (can be inferred also from the interface signature).
For pure reference, if none of the fields in the where clause is a key, then SQL workbench might reject your query if the SQL_SAFE_UPDATES options is set. That is because you can't update or delete records without specifying a key (example, primary key) in the where clause.
That's not possible because annotation attribute value must be constant and Enum is not a constant. But you can use java8 interface default method as a trick to solve it. The template code:
public interface TheEntityJpaRepository extends JpaRepository<TheEntity, Long> {
#Modifying(clearAutomatically = true)
#Query("UPDATE TheEntity SET status = :status WHERE id = :value")
int markToStatus(#Param("value") Long value, #Param("status") TheStatusEnum status);
default int markAsResolved(Long value) {
return markToStatus(value, TheStatusEnum.RESOLVED);
}
}
#Entity
public class YourEntity {
...
#Enumerated(EnumType.STRING)
private Status status;
...
}
public enum TheStatusEnum {
RESOLVED,
...
}
But you should know that this way will exposed the markToStatus method.
try this
#Modifying
#Query(value = "update table set status=:#{#status.toString()} where id = :id", nativeQuery = true)
void reset(#Param("id") Long id, #Param("status") TaskStatusEnum status);
you can follow the below process and come out to a solution.
//ENUM structure
#Getter
public enum Status {
RESOLVED(1, "resolved"),
NOT_RESOLVED(2, "not reolved");
private Integer id;
private String name;
Status(Integer id, String name) {
this.id = id;
this.name = name;
}
private static Status getById(Integer id) {
for (Status type : Status.values()) {
if (type.getId() == id) {
return type;
}
}
return null;
}
#Converter(autoApply = true)
public static class TypeConverter implements AttributeConverter<Status, Integer> {
#Override
public Integer convertToDatabaseColumn(Status type) {
return type != null ? type.getId() : null;
}
#Override
public Status convertToEntityAttribute(Integer id) {
return id != null ? Status.getById(id) : null;
}
}
}
//Entity Structure
#AllArgsConstructor
#NoArgsConstructor
#Getter
#Setter
#Builder(builderClassName = "Builder", toBuilder = true)
#Entity
#Table(name = "something")
public class Something {
private static final long serialVersionUID = 1696981698111303897L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", nullable = false, updatable = false)
protected Long id;
#Column(name = "status_id")
#Convert(converter = Status.TypeConverter.class)
private Status status;
}
//Service method
List<Something> findByIdAndStatus(Long id, Status status);
//impl method
#Override
public List<Something> findByIdAndStatus(Long id, Status status) {
return somthingRepository.findByIdAndStatus(id, status);
}
//repository method
List<Something> findByIdAndStatus(Long id, Status status);
//now call it like this.Note you are passing the enum value here.In this way you can pass any enum value to your JPA query or repository method.
List<Something> yourResult = somethingService.findByIdAndStatus(id,
Status.RESOLVED);

JPA - Select when parameter is a member of attribute, attribute is a set of string

NoticeGroupEntity:
#Id
#Column(name = "id")
private long id;
#Column(name = "name")
private String name;
#Column(name = "allowed_user_statuses")
#Convert(converter = StringSetConverter.class)
private Set<String> allowedUserStatuses;
This is StringSetConverter
public class StringSetConverter implements AttributeConverter<Set<String>, String> {
#Override
public String convertToDatabaseColumn(Set<String> list) {
return String.join(",", list);
}
#Override
public Set<String> convertToEntityAttribute(String joined) {
return new HashSet<>(Arrays.asList(joined.split(",")));
}
I try to select NoticeGroupEntity by using JPA:
#Query("SELECT ng FROM #{#entityName} ng WHERE ?1 IN ng.allowedUserStatuses")
List<NoticeGroupEntity> findByAllowedUserStatus(String userStatus);
Any way to select? It does not return any entity.
Thanks.
Your #Query is so strange.
Try this:
#Query("SELECT ng FROM NoticeGroupEntity ng WHERE ?1 member ng.allowedUserStatuses")
The above work for entity. I haven't tried with primitive type yet.
In case it does not work, native query will come to rescue:
#Query(value = "SELECT ng FROM NoticeGroupEntity ng WHERE ng.allowed_user_statuses LIKE ?1", native = true)
And pass your parameter as "%" + status + "%"

Categories

Resources