How to use optional attributes in Spring Data / Webflux - spring-data

I´m trying to work with joins in Spring-Webflux. I have two tables, comments and votes.
My Comment Entity has an attribute named score, which is the calculated number of votes.
The problem is this score isn´t a field inside the database, but at the moment a transient marked field in the Comment Object which is calculated by my application with bad performance.
My goal is to calculate this with a Join and not in my Application.
My Problem is that Spring doesn´t map the score field because of the transient annotation, which is needed because other Operations (patch or update) doesn´t provide this score field.
My Repository looks like this:
#Repository
public interface CommentRepository extends ReactiveCrudRepository<Comment, UUID> {
#Query("SELECT C.*, COALESCE(SUM(v.vote), 0) as score FROM comment c LEFT JOIN vote v ON c.id = v.comment_id WHERE c.room_id = $1 GROUP BY c.id")
Flux<Comment> findByRoomIdWithScore(UUID roomId);
Flux<Comment> findByRoomId(UUID roomId);
#Transactional
Flux<Void> deleteByRoomId(UUID roomId);
}
and my Comment Object is this:
#Table
public class Comment implements Persistable<UUID> {
#Id
private UUID id;
private UUID roomId;
private UUID creatorId;
private String body;
private Timestamp timestamp;
private boolean read;
private boolean favorite;
private int correct;
private boolean ack;
#Transient
private int score;
private String tag;
private String answer;
#Override
public boolean isNew() {
return id == null;
}
public UUID getId() {
return id;
}
public void setId(UUID id) {
this.id = id;
}
public UUID getRoomId() {
return roomId;
}
public void setRoomId(UUID roomId) {
this.roomId = roomId;
}
public UUID getCreatorId() {
return creatorId;
}
public void setCreatorId(UUID creatorId) {
this.creatorId = creatorId;
}
public String getBody() {
return body;
}
public void setBody(String body) {
this.body = body;
}
public Timestamp getTimestamp() {
return timestamp;
}
public void setTimestamp(Timestamp timestamp) {
this.timestamp = timestamp;
}
public boolean isRead() {
return read;
}
public void setRead(boolean read) {
this.read = read;
}
public boolean isFavorite() {
return favorite;
}
public void setFavorite(boolean favorite) {
this.favorite = favorite;
}
public int getCorrect() {
return correct;
}
public void setCorrect(int correct) {
this.correct = correct;
}
public boolean isAck() {
return ack;
}
public void setAck(boolean ack) {
this.ack = ack;
}
public int getScore() {
return score;
}
public void setScore(int score) {
this.score = score;
}
public String getTag() {
return tag;
}
public void setTag(String tag) {
this.tag = tag;
}
public String getAnswer() {
return answer;
}
public void setAnswer(String answer) {
this.answer = answer;
}
#Override
public String toString() {
return "Comment{" +
"id='" + id + '\'' +
", roomId='" + roomId + '\'' +
", creatorId='" + creatorId + '\'' +
", body='" + body + '\'' +
", timestamp=" + timestamp +
", read=" + read +
", favorite=" + favorite +
", correct=" + correct +
", ack=" + ack +
", score=" + score +
", tag=" + tag +
", answer=" + answer +
'}';
}
}
I´ve already tried to make another Comment class without the transient annotation, but that doesn´t work because of the used Repository i guess: reactor.core.Exceptions$ErrorCallbackNotImplemented: java.lang.IllegalArgumentException: Property must not be null!

The solution was to create another Class which extends Comment with the non transient score attribute. This could be casted back into a comment and the score is set

Related

JPQL count Parent Objects on Multiple Children Match in OneToMany Relationship

In a JavaEE JPA web application, Feature entity has bidirectional ManyToOne relationship with Patient Entity. I want to write a query to count the number of Patients who have one or more matching criteria features. I use EclipseLink as the Persistence Provider.
For example, I want to count the number of patients who have a feature with 'variableName' = 'Sex' and 'variableData' = 'Female' and another feature with 'variableName' = 'smoking' and 'variableData' = 'yes'.
How can I write a JPQL query to get the count of patients?
After the first answer, I tried this Query does not give any results as expected.
public void querySmokingFemales(){
String j = "select count(f.patient) from Feature f "
+ "where ((f.variableName=:name1 and f.variableData=:data1)"
+ " and "
+ " (f.variableName=:name2 and f.variableData=:data2))";
Map m = new HashMap();
m.put("name1", "sex");
m.put("data1", "female");
m.put("name2", "smoking");
m.put("data2", "yes");
count = getFacade().countByJpql(j, m);
}
The Patient entity is as follows.
#Entity
public class Patient implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
#OneToMany(mappedBy = "patient")
private List<Feature> features;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
#Override
public int hashCode() {
int hash = 0;
hash += (id != null ? id.hashCode() : 0);
return hash;
}
#Override
public boolean equals(Object object) {
// TODO: Warning - this method won't work in the case the id fields are not set
if (!(object instanceof Patient)) {
return false;
}
Patient other = (Patient) object;
if ((this.id == null && other.id != null) || (this.id != null && !this.id.equals(other.id))) {
return false;
}
return true;
}
#Override
public String toString() {
return "entity.Patient[ id=" + id + " ]";
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<Feature> getFeatures() {
return features;
}
public void setFeatures(List<Feature> features) {
this.features = features;
}
}
This is the Feature Entity.
#Entity
public class Feature implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String variableName;
private String variableData;
#ManyToOne
private Patient patient;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
#Override
public int hashCode() {
int hash = 0;
hash += (id != null ? id.hashCode() : 0);
return hash;
}
#Override
public boolean equals(Object object) {
// TODO: Warning - this method won't work in the case the id fields are not set
if (!(object instanceof Feature)) {
return false;
}
Feature other = (Feature) object;
if ((this.id == null && other.id != null) || (this.id != null && !this.id.equals(other.id))) {
return false;
}
return true;
}
#Override
public String toString() {
return "entity.Feature[ id=" + id + " ]";
}
public String getVariableName() {
return variableName;
}
public void setVariableName(String variableName) {
this.variableName = variableName;
}
public String getVariableData() {
return variableData;
}
public void setVariableData(String variableData) {
this.variableData = variableData;
}
public Patient getPatient() {
return patient;
}
public void setPatient(Patient patient) {
this.patient = patient;
}
}
For single feature counts you can use this
select count(f.patient) from Feature f where f.variableName=:name and f.variableData:=data
Two feature counts
select count(distinct p) from Patient p, Feature f1, Feature f2
where
p.id=f1.patient.id and p.id=f2.patient.id and
f1.variableName=:name1 and f1.variableData:=data1 and
f2.variableName=:name2 and f2.variableData:=data2
Multiple feature counts solution is a bit tricky. org.springframework.data.jpa.domain.Specification can be used
public class PatientSpecifications {
public static Specification<Patient> hasVariable(String name, String data) {
return (root, query, builder) -> {
Subquery<Fearure> subquery = query.subquery(Fearure.class);
Root<Fearure> feature = subquery.from(Fearure.class);
Predicate predicate1 = builder.equal(feature.get("patient").get("id"), root.get("id"));
Predicate predicate2 = builder.equal(feature.get("variableName"), name);
Predicate predicate3 = builder.equal(feature.get("variableData"), data);
subquery.select(operation).where(predicate1, predicate2, predicate3);
return builder.exists(subquery);
}
}
}
Then your PatientRepository have to extend org.springframework.data.jpa.repository.JpaSpecificationExecutor<Patient>
#Repository
public interface PatientRepository
extends JpaRepository<Patient, Long>, JpaSpecificationExecutor<Patient> {
}
Your service method:
#Service
public class PatientService {
#Autowired
PatientRepository patientRepository;
//The larger map is, the more subqueries query would involve. Try to avoid large map
public long countPatiens(Map<String, String> nameDataMap) {
Specification<Patient> spec = null;
for(Map.Entry<String, String> entry : nameDataMap.entrySet()) {
Specification<Patient> tempSpec = PatientSpecifications.hasVariable(entry.getKey(), entry.getValue());
if(spec != null)
spec = Specifications.where(spec).and(tempSpec);
else spec = tempSpec;
}
Objects.requireNonNull(spec);
return patientRepository.count(spec);
}
}
We also handled same situation for two feature and after extracting the IDs, we used a nested loops after and counting the number of common count. It was resource intensive and this two feature query in the answer helped a lot.
May need to redesign the Class Structure so that querying is easier.

Having trouble instantiating using Strings

I am trying to instantiate a student, gradstudent, and an MBAstudent in my main class and call the setter to set their first and last names, but im running into an error "The method setFirstName(String) in the type Student is not applicable for the arguments (Student)"
My Main class is as follows:
public class Main {
public static void main(String[] args) {
Student bob = new Student();
GradStudent john = new GradStudent();
MBAstudent michael = new MBAstudent();
bob.setFirstName(bob);
bob.setLastName(smith);
bob.setmNumber(1);
bob.setMatriculated(true);
john.setFirstName(john);
john.setLastName(white);
john.setmNumber(2);
john.setMatriculated(true);
john.setAge(23);
michael.setFirstName(michael);
michael.setLastName(scott);
michael.setmNumber(3);
michael.setMatriculated(true);
michael.setGpa(4.0);
}
}
My Student Class:
public class Student {
private String firstName;
private String lastName;
private int mNumber;
private boolean matriculated;
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public int getmNumber() {
return mNumber;
}
public void setmNumber(int mNumber) {
this.mNumber = mNumber;
}
public boolean isMatriculated() {
return matriculated;
}
public void setMatriculated(boolean matriculated) {
this.matriculated = matriculated;
}
public String toString() {
return (firstName + " " + lastName + " has an MNumber of " +
mNumber + " and is enrolled");
}
My GradStudent Class:
public class GradStudent extends Student {
private int age;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String toString() {
return (getFirstName() + " " + getLastName() + " has an MNumber of " +
getmNumber() + " and is " + age + " years old and is enrolled");
}
and my MBAstudent Class:
public class MBAstudent extends Student {
private double gpa;
public double getGpa() {
return gpa;
}
public void setGpa(double gpa) {
this.gpa = gpa;
}
public String toString() {
return (getFirstName() + " " + getLastName() + " has an MNumber of " +
getmNumber() + " and has a GPA of " + gpa + " years old and is enrolled");
}
you might be having this problem due to not putting your String parameters inside double-quotes(e.g., michael.setFirstName("michael");
michael.setLastName("scott");). Put all arguments of functions setFirstName() and setLastName() inside double-quotes and your problem will be solved. Have a nice day! You can also print each instance's members with get functions to be sure

A CallableStatement function was executed and the out parameter 1 was of type java.sql.Types=2001 however type java.sql.Types=1111 was registered

I am trying to call a STORED PROCEDURE(with a out refcursor parameter) in postgresql through JPA2.1 like this:
public class JpaINParam {
public static void main(String[] args) throws Exception {
EntityManagerFactory emfactory = Persistence
.createEntityManagerFactory("JPA2");
EntityManager entitymanager = emfactory.createEntityManager();
entitymanager.getTransaction().begin();
StoredProcedureQuery q = entitymanager.createNamedStoredProcedureQuery("get_hibernate_dtl");
q.setParameter("modeval", "1");
q.execute();
#SuppressWarnings("unchecked")
List<Student> students = (List<Student>) q.getOutputParameterValue("resultset");
for (Student student : students) {
System.out.println(student.getFname());
}
entitymanager.getTransaction().commit();
entitymanager.close();
try {
// storedProcedure.executeUpdate();
System.out.println("444444444444");
} catch (Exception e) {
e.printStackTrace();
}
}
}
i get the following error:
Exception in thread "main" javax.persistence.PersistenceException: Exception [EclipseLink-4002] (Eclipse Persistence Services -
2.5.2.v20140319-9ad6abd): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: org.postgresql.util.PSQLException: A CallableStatement function was executed and the out parameter 1 was of
type java.sql.Types=2001 however type java.sql.Types=1111 was
registered.
Error Code: 0
Call: {?= CALL get_hibernate_dtl(?)}
bind => [2 parameters bound]
Query: ResultSetMappingQuery(name="get_hibernate_dtl" )
at org.eclipse.persistence.internal.jpa.QueryImpl.getDetailedException(QueryImpl.java:378)
at org.eclipse.persistence.internal.jpa.QueryImpl.executeReadQuery(QueryImpl.java:260)
at org.eclipse.persistence.internal.jpa.StoredProcedureQueryImpl.execute(StoredProcedureQueryImpl.java:316)
at com.javacodegeeks.examples.jpa.service.JpaINParam.main(JpaINParam.java:36)
the following is my entity class:
#NamedStoredProcedureQuery(name="get_hibernate_dtl", procedureName="get_hibernate_dtl", resultClasses={Student.class}, returnsResultSet = true, parameters={
#StoredProcedureParameter(queryParameter="resultset", name="resultset", mode=ParameterMode.REF_CURSOR,type=Class.class),
#StoredProcedureParameter(queryParameter="modeval", name="modeval", mode=ParameterMode.IN,type=String.class)
})
#Entity
public class Student {
#Id
#GeneratedValue(strategy= GenerationType.AUTO)
private int sid;
private String fname;
private String lname;
private String dept;
private int year;
private String email;
public Student() {
// TODO Auto-generated constructor stub
}
public Student(int sid, String fname, String lname, String dept, int year,
String email) {
super();
this.sid = sid;
this.fname = fname;
this.lname = lname;
this.dept = dept;
this.year = year;
this.email = email;
}
public int getSid() {
return sid;
}
public void setSid(int sid) {
this.sid = sid;
}
public String getFname() {
return fname;
}
public void setFname(String fname) {
this.fname = fname;
}
public String getLname() {
return lname;
}
public void setLname(String lname) {
this.lname = lname;
}
public String getDept() {
return dept;
}
public void setDept(String dept) {
this.dept = dept;
}
public int getYear() {
return year;
}
public void setYear(int year) {
this.year = year;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
#Override
public String toString() {
return "Student [sid=" + sid + ", fname=" + fname + ", lname=" + lname
+ ", dept=" + dept + ", year=" + year + ", email=" + email
+ "]";
}
}
this is my procedure in postgresql:
CREATE OR REPLACE PROCEDURE dwh.get_hibernate_dtl(resultset OUT ahis_type.refcursor, modeval IN character varying DEFAULT '1'::character varying) AS
query VARCHAR2 (6000);
v_type NUMERIC(3,0);
v_dwh_type_id VARCHAR2(100);
BEGIN
IF (modeval = 1) THEN
QUERY := ' SELECT sid,fname FROM STUDENT ';
END IF;
INSERT INTO tmp_table values ( 'get_hibernate_dtl----Modeval-->'||modeval||'query--->'||QUERY );
OPEN resultset FOR QUERY;
END

Spring Batch:- One reader multiple writers

File structure I have a flat file which has two types of data. such as I have a Comma delimited file that has information of a students. There are multiple files like class A students, class B students etc.
Data in file is like first line has the class information and the rest has student information. So I want to put the class information to class table and student information to student table.
First line would always be of HeaderDataDAO type and all other would be of StudentDataDAO.
How can I achieve this?
My File structure
Class-A*10*2013-14
1*Stone*Barrett*1964-10-19
2*Armando*Stone*1973-02-04****
3*Armando*Logan*1986-12-25
4*Latifah*Barnett*1959-07-24
5*Cassandra*Moses*1956-09-14*********
6*Audra*Hopkins*1984-08-30
7*Upton*Morrow*1973-02-04
8*Melodie*Velasquez*1953-04-26
9*Sybill*Nolan*1951-06-24*****
10*Glenna*Little*1953-08-27
StudentDataDAO.java
public class StudentDataDAO {
private long id;
private String firstName;
private String lastName;
private Date birthDate;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public Date getBirthDate() {
return birthDate;
}
public void setBirthDate(String birthDate) throws ParseException {
if (!birthDate.equalsIgnoreCase("")) {
SimpleDateFormat spf = new SimpleDateFormat("yyyy-MM-dd");
this.birthDate = spf.parse(birthDate);
} else {
this.birthDate = null;
}
}
#Override
public String toString() {
return "CustomerData [id=" + id + ", firstName=" + firstName + ", lastName=" + lastName + ", birthDate=" + birthDate
+ "]";
}
}
HeaderDataDAO.java
public class HeaderDataDAO {
private String className;
private String numberOfStudent;
private String batch;
public String getClassName() {
return className;
}
public void setClassName(String className) {
this.className = className;
}
public String getNumberOfStudent() {
return numberOfStudent;
}
public void setNumberOfStudent(String numberOfStudent) {
this.numberOfStudent = numberOfStudent;
}
public String getBatch() {
return batch;
}
public void setBatch(String batch) {
this.batch = batch;
}
#Override
public String toString() {
return "HeaderData [className=" + className + ", numberOfStudent=" + numberOfStudent + ", batch=" + batch + "]";
}
}
You can use CompositeItemWriter below is sample config
<bean id="compositeWriter"
class="org.springframework.batch.item.support.CompositeItemWriter">
<property name="delegates">
<list>
<ref bean="classWriter" />
<ref bean="studentWriter" />
</list>
</property>
</bean>
Using CompositeItemWriter. Find sample over here: https://github.com/spring-projects/spring-batch/blob/master/spring-batch-samples/src/main/resources/jobs/compositeItemWriterSampleJob.xml
http://grepcode.com/file/repo1.maven.org/maven2/org.springframework.batch/spring-batch-samples/1.0.1.RELEASE/jobs/compositeItemWriterSampleJob.xml

Rest Client: Javax.ws.rs

i'm starting with Rest and don't have no idea how to implement it properly. I got an exercise: i must implement a Rest-Client with the RestClient-API from javax.ws.rs standard library and i tried by using the code below, but i'm getting a null pointer exception. But the resource are there and when i try directly from the browser (http://localhost:8080/sep/rest/customers/112). Now my question how can i do it properly. Some constraints, i must use XML (not JSON) for the Data-support.
Hier my client-code:
public Response createCustomer(Customer customer){
log.info("Starting: Rest Create a Customer with Name: " + Customer.class.getName());
this.customerWebTarget = this.client.target(URL);
Response response = this.customerWebTarget.request().
buildPost(Entity.entity(customer, MediaType.APPLICATION_XML)).invoke();
log.info("Ending: Rest Create a Customer with Name: " + response.getEntity().getClass().getName());
return response;
}
CustomerResource-Code:
#Path("customers")
public class CustomerResource implements IAllowedMethods<Customer> {
private static final long serialVersionUID = -6367055402693237329L;
private Logger logger = Logger.getLogger(CustomerResource.class.getName());
#Inject
private CustomerService service;
public CustomerResource() {
logger.info("create of instance " + this.getClass().getName());
}
#Override
#GET
#Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
public Response get() {
List<Customer> list = service.loadAll(Customer.FINDALL, Customer.class);
if (list != null && !list.isEmpty()) {
ResponseCustomerList responseList = new ResponseCustomerList();
responseList.setList(list);
return Response.ok(responseList).build();
}
return Response.status(Status.NOT_FOUND).build();
}
.
.
.
Customer Code:
import de.ostfalia.sep.adapter.XMLIntegerAdapter;
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class Customer implements Serializable {
private static final long serialVersionUID = 80668466040239995L;
#XmlID
#XmlJavaTypeAdapter(XMLIntegerAdapter.class)
private Integer customerNumber;
private String customerName;
private String contactLastName;
private String contactFirstName;
private String phone;
private String addressLine1;
private String addressLine2;
private String city;
private String state;
private String postalCode;
private String country;
#XmlIDREF
private Employee salesRepEmployee;
private BigDecimal creditLimit;
private Set<Payment> payments;
private Set<Order> orders;
public Customer() {
}
public Customer(Integer customernumber) {
this.customerNumber = customernumber;
}
public Customer(Integer customerNumber, String customerName, String contactLastName, String contactFirstName,
String phone, String addressLine1, String city, String country) {
this.customerNumber = customerNumber;
this.customerName = customerName;
this.contactLastName = contactLastName;
this.contactFirstName = contactFirstName;
this.phone = phone;
this.addressLine1 = addressLine1;
this.city = city;
this.country = country;
}
public Integer getCustomerNumber() {
return customerNumber;
}
public void setCustomerNumber(Integer customerNumber) {
this.customerNumber = customerNumber;
}
public String getCustomerName() {
return customerName;
}
public void setCustomerName(String customerName) {
this.customerName = customerName;
}
public String getContactLastName() {
return contactLastName;
}
public void setContactLastName(String contactLastName) {
this.contactLastName = contactLastName;
}
public String getContactFirstName() {
return contactFirstName;
}
public void setContactFirstName(String contactFirstName) {
this.contactFirstName = contactFirstName;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getAddressLine1() {
return addressLine1;
}
public void setAddressLine1(String addressLine1) {
this.addressLine1 = addressLine1;
}
public String getAddressLine2() {
return addressLine2;
}
public void setAddressLine2(String addressLine2) {
this.addressLine2 = addressLine2;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
public String getPostalCode() {
return postalCode;
}
public void setPostalCode(String postalCode) {
this.postalCode = postalCode;
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
public Employee getSalesRepEmployee() {
return salesRepEmployee;
}
public void setSalesRepEmployee(Employee salesRepEmployee) {
this.salesRepEmployee = salesRepEmployee;
}
public BigDecimal getCreditLimit() {
return creditLimit;
}
public void setCreditLimit(BigDecimal creditLimit) {
this.creditLimit = creditLimit;
}
#Override
public int hashCode() {
int hash = 0;
hash += (customerNumber != null ? customerNumber.hashCode() : 0);
return hash;
}
#Override
public boolean equals(Object object) {
// TODO: Warning - this method won't work in the case the id fields are
// not set
if (!(object instanceof Customer)) {
return false;
}
Customer other = (Customer) object;
if ((this.customerNumber == null && other.customerNumber != null)
|| (this.customerNumber != null && !this.customerNumber.equals(other.customerNumber))) {
return false;
}
return true;
}
#Override
public String toString() {
return customerNumber.toString();
}
public Set<Payment> getPayments() {
return payments;
}
public void setPayments(Set<Payment> payments) {
this.payments = payments;
}
public Set<Order> getOrders() {
return orders;
}
public void setOrders(Set<Order> orders) {
this.orders = orders;
}
}
Instead of response.getEntity(), use response.readEntity(String.class) to get the data as a String. If you want to deserialize it to a POJO, then just pass that class to the readEntity.
Also you should make sure to check the status code (response.getStatus()) to make sure it's a success status.