Stuck with Java 8 stream usage - java-8

I am trying to learn how to use Streams in Java 8 but am not sure how to go about it here.
I have a list of courses. I need to know if all the courses for a semester have no students, and if that is the case, do something. I came up with the below code but this is giving Null Pointer Exception as soon as any course gets iterated which doesn't have any students.I need to know how can i correct it:
List<Student> students = semester.getCourses().stream().flatMap(course -> course.getStudents().stream())
.filter(Objects :: nonNull).collect(toList());
if (CollectionUtils.isEmpty(students)){
//cancel the semester or do something
}
public class Semester{
int semId;
List<Course> courses;
}
public class Course{
int courseId;
List<Student> students;
}

In the actual code the NullPointerException may come from course is null or course.getStudents() is null.
This filter filter(Objects :: nonNull) is helpless. It doesn't filter the null Students and that is not your requirement.
This code should be what you are looking for :
List<Student> students =
semester.getCourses()
.stream()
.filter(Objects::nonNull) // filter out null Course objects
.map(Course::getStudents)
.filter(Objects::nonNull) // filter out null Student List
.flatMap(Collection::stream)
.collect(toList());
Note also that adding null checks everywhere is not good : it makes "real logic" less readable.
You can at least avoid these for collection fields by initializing them in their declarations such as :
public class Semester{
int semId;
List<Course> courses = new ArrayList<>();
}
public class Course{
int courseId;
List<Student> students = new ArrayList<>();
}

boolean allCourseHaveEmptyStudens=semester.getCourses().stream()
.allMatch(c-> c.getStudents()==null || c.getStudents().size==0)
;
I think it's enough to fulfill your requirement.
Note: It might get a compilation error because I don't use editor tool.

Related

comparing two different type of List using Java8 [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 12 months ago.
Improve this question
class Person{
private String name;
private String address;
private int id;
private int uniqueIdentificationNumber;
}
class Company{
private String name;
private String address;
private int id;
private int uniqueIdentificationNumber;
private String company;
}
class Test{
public static void main(String[] args)
{
List<Person> persons = new ArrayList<>();
Person p1 = new Person();
p1.setName("ABC");
p1.setAddress("US");
p1.setId(1);
p1.setUniqueIdentificationNumber(11);
Person p2 = new Person();
p2.setName("PQR");
p2.setAddress("US");
p2.setId(2);
p2.setUniqueIdentificationNumber(22);
persons.add(p1);
persons.add(p2);
List<Company> companies = new ArrayList<>();
Company c1 = new Comapny();
c1.setName("ABC");
c1.setAddress("US");
c1.setId(3);
c1.setUniqueIdentificationNumber(44);
c1.setCompany("C1")
Company c2 = new Comapny();
c2.setName("ABC");
c2.setAddress("US");
c2.setId(1);
c2.setUniqueIdentificationNumber(11);
c2.setCompany("C2");
companies.add(c1);
companies.add(c2)
}
}
I want to compare two different object types of lists (companies and persons) with Java8 Stream API and return the customer object which is matching with Id and setUniqueIdentificationNumber. i.e here in this case it should retun c2.
Can anyone help on this
It doesn't clear how uniqueIdentificationNumber of the Person and Company are related. It's worth to refine these classes to represent the relationship between them in a better way (maybe a company can hold a reference to a list of customers). And don't overuse setters, if id is unique there's no need to allow it to be changed.
Although it's not clear how these values are connected because of the drawbacks of your class design technically it's doable.
return the customer object which is matching with Id
For that, you need to create two maps that will associate these identifiers with companies and persons. Then create a stream over the keys of one of these maps and check for every key whether if contained in another map. And then retrieve the Person objects for filtered keys and collect the result into a list.
Map<Integer, Person> personById =
persons.stream()
.collect(Collectors.toMap(Person::getUniqueIdentificationNumber,
Function.identity()));
Map<Integer, Company> companyById =
companies.stream()
.collect(Collectors.toMap(Company::getUniqueIdentificationNumber,
Function.identity()));
List<Person> customers =
personById.keySet().stream()
.filter(companyById::containsKey) // checking whether id is present in the company map
.map(personById::get) // retrieving customers
.collect(Collectors.toList());
Update
Let me rephrase the description of the problem.
There are two unrelated classes A and B. Both classes have two fields of type int, let's say val1 and val2 (and maybe a few more fields but we are not interested in them).
We have a list of objects A and a list of objects B. The goal is to find a single object A for which exists an object B with the same values for both val1 and val2 (in order to keep things simple I propose to stick with this example).
There are two approaches that could be used for that purpose:
create an auxiliary class with two fields val1 and val2, and associate every instance of A and B with instances of this class;
create a nested map Map<Integer, Map<Integer, *targetClass*>>, this solution is more complicated and less flexible, if you'll need to compare objects by three, four, etc. fields the code will quickly become incomprehensible.
So I'll stick with the first approach. We need to declare the ValueHolder class with two fields and implement the equals/hashCode contract based on these fields. For Java 16 and above we can utilize a record for that purpose and make use of equals(), hashCode, getters provided by the compiler. The option with the record will look like this:
public record ValueHolder(int val1, int val2) {} // equals/hashCode, constructor and getters provided by the compiler
Classes A and B
public class A {
private int val1;
private int val2;
// constructor and getters
}
public class B {
private int val1;
private int val2;
// constructor and getters
}
And a method that accepts two lists: List<A> and List<B>, and return a result as Optional<A>. Because the matching element may or may not be present and it's a good practice to return an optional object in such cases instead of returning null in case the result was not found. It provides more flexibility and that's precisely the case for which the optional was designed.
public Optional<A> getMatchingItem(List<A> listA, List<B> listB) {
Map<ValueHolder, A> aByValue = listA.stream()
.collect(Collectors.toMap(a -> new ValueHolder(a.getVal1(), a.getVal2()),
Function.identity()));
Map<ValueHolder, B> bByValue = listB.stream()
.collect(Collectors.toMap(b -> new ValueHolder(b.getVal1(), b.getVal2()),
Function.identity()));
return aByValue.keySet().stream()
.filter(bByValue::containsKey)
.findFirst()
.map(aByValue::get);
}

QueryDSL Predicate for use with JPARepository where field is a JSON String converted using an AttributeConverter to a List<Object>

I have a JPA Entity (Terminal) which uses an AttributeConverter to convert a Database String into a list of Objects (ProgrmRegistration). The converter just uses a JSON ObjectMapper to turn the JSON String into POJO objects.
Entity Object
#Entity
#Data
public class Terminal {
#Id
private String terminalId;
#NotEmpty
#Convert(converter = ProgramRegistrationConverter.class)
private List<ProgramRegistration> programRegistrations;
#Data
public static class ProgramRegistration {
private String program;
private boolean online;
}
}
The Terminal uses the following JPA AttributeConverter to serialize the Objects from and to JSON
JPA AttributeConverter
public class ProgramRegistrationConverter implements AttributeConverter<List<Terminal.ProgramRegistration>, String> {
private final ObjectMapper objectMapper;
private final CollectionType programRegistrationCollectionType;
public ProgramRegistrationConverter() {
this.objectMapper = new ObjectMapper().setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
this.programRegistrationCollectionType =
objectMapper.getTypeFactory().constructCollectionType(List.class, Terminal.ProgramRegistration.class);
}
#Override
public String convertToDatabaseColumn(List<Terminal.ProgramRegistration> attribute) {
if (attribute == null) {
return null;
}
String json = null;
try {
json = objectMapper.writeValueAsString(attribute);
} catch (final JsonProcessingException e) {
LOG.error("JSON writing error", e);
}
return json;
}
#Override
public List<Terminal.ProgramRegistration> convertToEntityAttribute(String dbData) {
if (dbData == null) {
return Collections.emptyList();
}
List<Terminal.ProgramRegistration> list = null;
try {
list = objectMapper.readValue(dbData, programRegistrationCollectionType);
} catch (final IOException e) {
LOG.error("JSON reading error", e);
}
return list;
}
}
I am using Spring Boot and a JPARepository to fetch a Page of Terminal results from the Database.
To filter the results I am using a BooleanExpression as the Predicate. For all the filter values on the Entity it works well, but the List of objects converted from the JSON string does not allow me to easily write an Expression that will filter the Objects in the list.
REST API that is trying to filter the Entity Objects using QueryDSL
#GetMapping(path = "/filtered/page", produces = MediaType.APPLICATION_JSON_VALUE)
public Page<Terminal> findFilteredWithPage(
#RequestParam(required = false) String terminalId,
#RequestParam(required = false) String programName,
#PageableDefault(size = 20) #SortDefault.SortDefaults({ #SortDefault(sort = "terminalId") }) Pageable p) {
BooleanBuilder builder = new BooleanBuilder();
if (StringUtils.isNotBlank(terminalId))
builder.and(QTerminal.terminal.terminalId.upper()
.contains(StringUtils.upperCase(terminalId)));
// TODO: Figure out how to use QueryDsl to get the converted List as a predicate
// The code below to find the programRegistrations does not allow a call to any(),
// expects a CollectionExpression or a SubqueryExpression for calls to eqAny() or in()
if (StringUtils.isNotBlank(program))
builder.and(QTerminal.terminal.programRegistrations.any().name()
.contains(StringUtils.upperCase(programName)));
return terminalRepository.findAll(builder.getValue(), p);
}
I am wanting to get any Terminals that have a ProgramRegistration object with the program name equal to the parameter passed into the REST service.
I have been trying to get CollectionExpression or SubQueryExpression working without success since they all seem to be wanting to perform a join between two Entity objects. I do not know how to create the path and query so that it can iterate over the programRegistrations checking the "program" field for a match. I do not have a QProgamRegistration object to join with, since it is just a list of POJOs.
How can I get the predicate to match only the Terminals that have programs with the name I am searching for?
This is the line that is not working:
builder.and(QTerminal.terminal.programRegistrations.any().name()
.contains(StringUtils.upperCase(programName)));
AttributeConverters have issues in Querydsl, because they have issues in JPQL - the query language of JPA - itself. It is unclear what actually the underlying query type of the attribute is, and whether the parameter should be a basic type of that query type, or should be converted using the conversion. Such conversion, whilst it appears logical, is not defined in the JPA specification. Thus a basic type of the query type needs to be used instead, which leads to new difficulties, because Querydsl can't know the type it needs to be. It only knows the Java type of the attribute.
A workaround can be to force the field to result into a StringPath by annotating the field with #QueryType(PropertyType.STRING). Whilst this fixes the issue for some queries, you will run into different issues in other scenarios. For more information, see this thread.
Although the following desired QueryDsl looks like it should work
QTerminal.terminal.programRegistrations.any().name().contains(programName);
In reality JPA would never be able to convert it into something that would make sense in terms of SQL. The only SQL that JPA could convert it into could be as follows:
SELECT t.terminal_id FROM terminal t where t.terminal_id LIKE '%00%' and t.program_registrations like '%"program":"MY_PROGRAM_NAME"%';
This would work in this use case, but be semantically wrong, and therefore it is correct that it should not work. Trying to select unstructured data using a structured query language makes no sense
The only solution is to treat the data as characters for the DB search criteria, and to treat it as a list of Objects after the query completes and then perform filtering of the rows in Java. Although This makes the paging feature rather useless.
One possible solution is to have a secondary read only String version of the column that is used for the DB search criteria, that is not converted to JSON by the AttributeConverter.
#JsonIgnore
#Column(name = "programRegistrations", insertable = false, updatable = false)
private String programRegistrationsStr;
The real solution is do not use unstructured data when you want structured queries on that data Therefore convert the data to either a database that supports the JSON natively for queries or model the data correctly in DDL.
To have a short answer: the parameter used in the predicate on attribute with #QueryType must be used in another predicate on attribute of type String.
It's a clearly known issue describe in this thread: https://github.com/querydsl/querydsl/issues/2652
I simply want to share my experience about this bug.
Model
I have an entity like
#Entity
public class JobLog {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private String id;
#QueryType(PropertyType.STRING)
private LocalizedString message;
}
Issue
I want to perform some predicate about message. Unfortunately, with this configuration, I can't do this:
predicates.and(jobLog.message.likeIgnoreCase(escapedTextFilter));
because I have the same issues that all people!
Solution
But I find a way to workaround :)
predicates.and(
(jobLog.id.likeIgnoreCase(escapedTextFilter).and(jobLog.id.isNull()))
.or(jobLog.message.likeIgnoreCase(escapedTextFilter)));
Why it workaround the bug?
It's important that escapedTextFilter is the same in both predicate!
Indeed, in this case, the constant is converter to SQL in the first predicate (which is of String type). And in the second predicate, we use the conterted value
Bad thing?
Add a performance overflow because we have OR in predicate
Hope this can help someone :)
I've found one way to solve this problem, my main idea is to use mysql function cast(xx as char) to cheat hibrenate. Below is my base info. My code is for work , so I've made an example.
// StudentRepo.java
public interface StudentRepo<Student, Long> extends JpaRepository<Student, Long>, QuerydslPredicateExecutor<Student>, JpaSpecificationExecutor<Student> {
}
// Student.java
#Data
#AllArgsConstructor
#NoArgsConstructor
#EqualsAndHashCode(of = "id")
#Entity
#Builder
#Table(name = "student")
public class Student {
#Convert(converter = ClassIdsConvert.class)
private List<String> classIds;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
}
// ClassIdsConvert.java
public class ClassIdsConvert implements AttributeConverter<List<String>, String> {
#Override
public String convertToDatabaseColumn(List<String> ips) {
// classid23,classid24,classid25
return String.join(",", ips);
}
#Override
public List<String> convertToEntityAttribute(String dbData) {
if (StringUtils.isEmpty(dbData)) {
return null;
} else {
return Stream.of(dbData.split(",")).collect(Collectors.toList());
}
}
}
my db is below
id
classIds
name
address
1
2,3,4,11
join
北京市
2
2,31,14,11
hell
福建省
3
2,12,22,33
work
福建省
4
1,4,5,6
ouy
广东省
5
11,31,34,22
yup
上海市
-- ----------------------------
-- Table structure for student
-- ----------------------------
DROP TABLE IF EXISTS `student`;
CREATE TABLE `student` (
`id` int(11) NOT NULL,
`classIds` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`address` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;
Use JpaSpecificationExecutor solve the problem
Specification<Student> specification = (root, query, criteriaBuilder) -> {
String classId = "classid24"
String classIdStr = StringUtils.wrap(classId, "%");
var predicate = criteriaBuilder.like(root.get("classIds").as(String.class), classIdStr);
return criteriaBuilder.or(predicate);
};
var students = studentRepo.findAll(specification);
log.info(new Gson().toJson(students))
attention the code root.get("classIds").as(String.class)
In my opinion, if I don't add .as(String.class) , hibernate will think the type of student.classIds is list and throw an Exception as below.
SQL will like below which runs correctly in mysql. But hibnerate can't work.
org.springframework.dao.InvalidDataAccessApiUsageException: Parameter value [%classid24%] did not match expected type [java.util.List (n/a)]; nested exception is java.lang.IllegalArgumentException: Parameter value [%classid24%] did not match expected type [java.util.List (n/a)]
SELECT
student0_.id AS id1_0_,
student0_.class_ids AS class_ids2_0_
FROM
student student0_
WHERE
student0_.class_ids LIKE '%classid24%' ESCAPE '!'
if you add .as(String.class) , hibnerate will think the type of student.classIds as string and won't check it at all.
SQL will be like below which can run correct in mysql. Also in JPA.
SELECT
student0_.id AS id1_0_,
student0_.class_ids AS class_ids2_0_
FROM
student student0_
WHERE
cast( student0_.class_ids AS CHAR ) LIKE '%classid24%' ESCAPE '!'
when the problem is solved by JpaSpecificationExecutor, so I think this can be solve also in querydsl. At last I find the template idea in querydsl.
String classId = "classid24";
StringTemplate st = Expressions.stringTemplate("cast({0} as string)", qStudent.classIds);
var students = Lists.newArrayList<studentRepo.findAll(st.like(StringUtils.wrap(classId, "%"))));
log.info(new Gson().toJson(students));
it's sql is like below.
SELECT
student0_.id AS id1_0_,
student0_.class_ids AS class_ids2_0_
FROM
student student0_
WHERE
cast( student0_.class_ids AS CHAR ) LIKE '%classid24%' ESCAPE '!'

SpringBoot: Is this correct way to save a new entry which has ManyToOne relationship?

I have two entities Person and Visit
Person has OneToMany relationship with Visit.
I was wondering if I want to save an new entry of Visit, and interm of using RestController. Is my approach correct? Or is there another way which is more efficient?
So I have the following controller which takes a VisitModel from the RequestBody, is it a correct way to call it like so?
VisitModel has the ID of person, and the needed properties for the Visit entity. I use the ID of person to look up in the personRepository for the related Person entry, whereafter I issue it to a new instance of Visit and then use the visitRepository to save it.
#RequestMapping(value="", method=RequestMethod.POST)
public String checkIn(#RequestBody VisitModel visit) {
Person person = personRepository.findById(visit.personId);
Visit newVisit = new Visit(visit.getCheckIn, person);
visitRepository.save(newVisit);
return "success";
}
The Visit entity looks as following
#Entity
public class Visit {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
#JsonProperty("check_in")
private Date checkIn;
#JsonProperty("check_out")
private Date checkOut;
#ManyToOne
#JoinColumn(name="personId")
private Person person;
public Visit(Date checkIn, Person person) {
this.checkIn = checkIn;
this.person = person;
}
public Date getCheckIn() {
return checkIn;
}
public void setCheckIn(Date checkIn) {
this.checkIn = checkIn;
}
public Date getCheckOut() {
return checkOut;
}
public void setCheckOut(Date checkOut) {
this.checkOut = checkOut;
}
public Person getPerson() {
return person;
}
}
I want to know of the following approach is correct. Or is there another way which is better?
You don't need to get a Person from the database to associate it with a Visit, of course. Because of, you need to have only id of a Person to save it in the foreign key column personId.
If you use JPA EntityManager
Person person = entityManager.getReference(Person.class, visit.personId);
for Hibernate Session
Person person = session.load(Person.class, visit.personId);
This methods just create a proxy and don't do any database requests.
With Hibernate Session I used new Person(personId) as #MadhusudanaReddySunnapu suggested. Everything worked fine.
What is the difference between EntityManager.find() and EntityManger.getReference()?
Hibernate: Difference between session.get and session.load
Yes, that seems to me to be the standard way to map a bidirectional relationship. EDIT: The personId column points to the "id" field of the Person entity.Eg:
#Id
private Long id;
UPDATE: 1: The VisitModel is a 'DTO' or Data Transfer Object. Any separate package is fine. You could consider putting them into a separate jar, so that anyone using your API (with java) can use the jar to create the data before making the call. 2) The way you save it is fine as far as I can see.

jpa pass reserved words in #Param String

My goal is to write a query like:
select * from Book where author = any(select author from book)
and(genre='comedy') order by ( '' )ASC, ( pages )DESC;
Where 'any(select author from book)' loses the single quotes so I can pass it thus
#Query("select b from Book b where b.author =:author and b.genre =:genre")
List<Book> findAllBySearch(#Param("author") String author,
#Param("genre") String genre);
The reason for doing this is because I have a form with 5 input criteria that may or may not be present and I don't want to write a separate query for each permutation. I know that one query with either the 'stringInput' or 'any(select criteria from book)' in case or null or empty string inserted before running the query.
I suppose using criteria API or something like that would allow building dynamic Query or inserting reserved sql words but I don't know how to implement it easily since I'm extending Spring data CrudRepository not using entitymanager...yet.... I suppose I will have to.
Does anyone know how to escape the '' imposed by string input #Param or what approach would easily work... such as Criteria API, Stored Procedure, Function ?
Please excuse my inexperience here...
Thanks !
It looks like the only way to do this with 5 possible inputs is to have the runtime strategy choose from 14 different queries depending on what inputs are present but I was trying to avoid this !!
So... here's the solution !
/**
* A JPA-based implementation of the Book Service. Delegates to a JPA entity
* manager to issue data access calls against the backing repository. The
* EntityManager reference is provided by the managing container (Spring)
* automatically.
*/
#Service("bookService")
#Repository
public class JpaBookService implements BookService {
private EntityManager em;
#PersistenceContext
public void setEntityManager(EntityManager em) {
this.em = em;
}
public List<Book> searchBooks(String author, String genre, String pages, String year, String rating){
List<Predicate> predList = new LinkedList<Predicate>();
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Book> c =cb.createQuery(Book.class);
Root<Book> b = c.from(Book.class);
if(author!="")
predList.add(cb.equal(b.get("author"), author));
}
if(genre!=""){
predList.add(cb.equal(b.get("genre"), genre));
}
if(pages!=""){
predList.add(cb.equal(b.get("pages"), pages));
}
if(year!=""){
predList.add(cb.equal(b.get("year"), year));
}
if(rating!=""){
predList.add(cb.equal(b.get("rating"), rating));
}
Predicate[] predArray = new Predicate[predList.size()];
predList.toArray(predArray);
c.where(predArray);
TypedQuery<Book> q = em.createQuery(c);
List<Book> result = q.getResultList();
return result;
}
}
Works good !
Johnny O.

Using Criteria API or java.util.List contains() method?

Lets assume the following situation: I have two entity classes Person and Comment.
//Person class
#Entity
public class Person extends AbstractBusinesObject{
#OneToMany(mappedby ="owner")
private List<Comment> comments;
//Comment class
#Entity
public class Comment extends AbstractBusinesObject{
#ManyToOne
private Person owner;
I would like to know which approach is better (performance, memory-efficiency) from the following two alternatives to determine whether a specific comment belongs to a specific person or not.
public boolean isUsersComment(Person p, Comment c){
return p.getComments().contains(c);
}
or
public boolean isUsersComment(Person p, Comment c){
CriteriaBuilder cb = getEntitymanager().getCriteriaBuilder();
CriteriaQuery<Comment> cq = cb.createQuery(Comment.class);
Root<Comment> root = cq.from(Comment.class);
cq.select(root).where(cb.and(cb.equal(root, c), cb.isTrue(root.in(p.getComments())));
try{
getEntityManager().createQuery(cq).getSingleResult();
return true;
} catch (NoResultException ex){
return false;
} catch (NoUniqueResultException ex){
throw new IllegalStateException(message);
}
}
As far as i know criteria api generates a database query, while the first solution searches in a collection.
Thank you for your answer.
ps: I am new to Criteria API, so I also accept fixes/suggestions.
Edit
Ok, I have found out it was a stupid question; As I declared a bidirectional relationship, the solution is very simple:
return c.getOwner.equals(p);
Anyway, what is the approach in case of unidirectional relationship?
Depends if the original Collection is already loaded, no? If the Collection is loaded then its a straight contains() call, otherwise it likely would be the equivalent of what a query (Criteria or JPQL) would do.
If the Collection was large then I'd go through a query always (since I wouldn't want to load the whole collection). Otherwise I'd use Collection.contains

Resources