Filter Java 8 list to contain elements based on thresholds defined in other list - java-8

I have a sample program below where subject passing marks thresholds are defined in Metadata. The studentMarks list should be filtered in a way so that the resulting list will contain all the subjects of a student where the marks scored are greater than the threshold in at least one subject.
import lombok.Builder;
import lombok.Getter;
import java.util.ArrayList;
import java.util.List;
public class TestClass {
public static void main(String[] args) {
TestClass test = new TestClass();
List<Metadata> metadataList = test.buildMetadata();
List<StudentMarks> studentMarks = test.buildStudentMarks();
// filter studentMarks list so that the resulting list will contain
// all records (subjects) of first and second students because
// they scored above the threshold in at least one of the subjects
}
private List<StudentMarks> buildStudentMarks() {
List<StudentMarks> studentMarks = new ArrayList<>();
StudentMarks firstStudMath = StudentMarks.builder().subject("MATH").marks(93).build();
StudentMarks firstStudEng = StudentMarks.builder().subject("ENG").marks(45).build();
StudentMarks firstStudPE = StudentMarks.builder().subject("PE").marks(80).build();
StudentMarks secondStudMath = StudentMarks.builder().subject("MATH").marks(74).build();
StudentMarks secondStudEng = StudentMarks.builder().subject("ENG").marks(59).build();
StudentMarks secondStudPE = StudentMarks.builder().subject("PE").marks(88).build();
StudentMarks thirdStudMath = StudentMarks.builder().subject("MATH").marks(50).build();
StudentMarks thirdStudEng = StudentMarks.builder().subject("ENG").marks(50).build();
StudentMarks thirdStudPE = StudentMarks.builder().subject("PE").marks(50).build();
studentMarks.add(firstStudMath);
studentMarks.add(firstStudEng);
studentMarks.add(firstStudPE);
studentMarks.add(secondStudMath);
studentMarks.add(secondStudEng);
studentMarks.add(secondStudPE);
studentMarks.add(thirdStudMath);
studentMarks.add(thirdStudEng);
studentMarks.add(thirdStudPE);
return studentMarks;
}
private List<Metadata> buildMetadata() {
List<Metadata> metadataList = new ArrayList<>();
Metadata math = Metadata.builder().subject("MATH").marksThreshold(75).build();
Metadata english = Metadata.builder().subject("ENG").marksThreshold(60).build();
Metadata pe = Metadata.builder().subject("PE").marksThreshold(80).build();
// more subjects
metadataList.add(math);
metadataList.add(english);
metadataList.add(pe);
return metadataList;
}
}
#Getter #Builder
class StudentMarks {
private Integer rollNum; //unique identifier
private String subject;
private Integer marks;
}
#Getter #Builder
class Metadata {
private String subject;
private Integer marksThreshold;
}
Based on the thresholds (Metadata), the output list would contain all the records for firstStud and secondStud BECAUSE these students scored more than the threshold in AT LEAST one subject. In the example above, the output list will have 6 records (2 passed students * 3 subjects each student)
The Metadata can have more or less subjects depending on the use case and there can be any number of students in studentMarks list. However, each student will always have same number of records (subjects) as defined in Metadata.
Update:
This is how I implemented this. Please suggest if this can be further optimized or shortened.
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class TestClass {
public static void main(String[] args) {
TestClass test = new TestClass();
List<Metadata> metadataList = test.buildMetadata();
List<StudentMarks> studentMarks = test.buildStudentMarks();
// filter studentMarks list so that the resulting list will contain
// all records (subjects) of first and second students because
// they scored above the threshold in at least one of the subjects
Map<Integer, List<StudentMarks>> lookup =
studentMarks.stream().collect(Collectors.groupingBy(StudentMarks::getRollNum));
System.out.println(lookup);
// Output:
//{1=[StudentMarks(rollNum=1, subject=MATH, marks=93), StudentMarks(rollNum=1, subject=ENG, marks=45), StudentMarks(rollNum=1, subject=PE, marks=80)],
// 2=[StudentMarks(rollNum=2, subject=MATH, marks=74), StudentMarks(rollNum=2, subject=ENG, marks=59), StudentMarks(rollNum=2, subject=PE, marks=88)],
// 3=[StudentMarks(rollNum=3, subject=MATH, marks=50), StudentMarks(rollNum=3, subject=ENG, marks=50), StudentMarks(rollNum=3, subject=PE, marks=50)]}
lookup.forEach((k, v) -> {
boolean filter = true;
for (Metadata m : metadataList) {
StudentMarks sm = v.stream().filter(s -> s.getSubject().equalsIgnoreCase(m.getSubject())).findFirst().get();
if (m.getMarksThreshold() <= sm.getMarks()) {
filter = false;
break;
}
}
if (!filter) System.out.println(v);
});
}
private List<StudentMarks> buildStudentMarks() {
List<StudentMarks> studentMarks = new ArrayList<>();
StudentMarks firstStudMath = StudentMarks.builder().rollNum(1).subject("MATH").marks(93).build();
StudentMarks firstStudEng = StudentMarks.builder().rollNum(1).subject("ENG").marks(45).build();
StudentMarks firstStudPE = StudentMarks.builder().rollNum(1).subject("PE").marks(80).build();
StudentMarks secondStudMath = StudentMarks.builder().rollNum(2).subject("MATH").marks(74).build();
StudentMarks secondStudEng = StudentMarks.builder().rollNum(2).subject("ENG").marks(59).build();
StudentMarks secondStudPE = StudentMarks.builder().rollNum(2).subject("PE").marks(88).build();
StudentMarks thirdStudMath = StudentMarks.builder().rollNum(3).subject("MATH").marks(50).build();
StudentMarks thirdStudEng = StudentMarks.builder().rollNum(3).subject("ENG").marks(50).build();
StudentMarks thirdStudPE = StudentMarks.builder().rollNum(3).subject("PE").marks(50).build();
studentMarks.add(firstStudMath);
studentMarks.add(firstStudEng);
studentMarks.add(firstStudPE);
studentMarks.add(secondStudMath);
studentMarks.add(secondStudEng);
studentMarks.add(secondStudPE);
studentMarks.add(thirdStudMath);
studentMarks.add(thirdStudEng);
studentMarks.add(thirdStudPE);
return studentMarks;
}
private List<Metadata> buildMetadata() {
List<Metadata> metadataList = new ArrayList<>();
Metadata math = Metadata.builder().subject("MATH").marksThreshold(75).build();
Metadata english = Metadata.builder().subject("ENG").marksThreshold(60).build();
Metadata pe = Metadata.builder().subject("PE").marksThreshold(80).build();
// more subjects
metadataList.add(math);
metadataList.add(english);
metadataList.add(pe);
return metadataList;
}
}
#Getter #Builder #ToString
class StudentMarks {
private Integer rollNum;
private String subject;
private Integer marks;
}
#Getter #Builder #ToString #EqualsAndHashCode(onlyExplicitlyIncluded = true)
class Metadata {
#EqualsAndHashCode.Include
private String subject;
private Integer marksThreshold;
}

To achieve your target, there is one important thing required, which is a field in the StudentMarks class, to uniquely identify your student. I am assuming that field to be rollNum. On the basis of that, we can achieve the desired result as follows:
Create a map of the student and his/her marks. (rollNum -> List<StudentMarks>);
Iterate over the entries of the map, and for each entry check, whether the student has marks in any one subject greater than the threshold.
Here is the code, for that:
import lombok.Builder;
import lombok.Getter;
import java.util.*;
public class Sample {
public static void main(String[] args) {
Sample test = new Sample();
List<Metadata> metadataList = test.buildMetadata();
List<StudentMarks> studentMarks = test.buildStudentMarks();
Map<Integer, List<StudentMarks>> rollNoToMarksMap = new HashMap<>();
// Create a map of student rollNum to his list of marks
for(StudentMarks marks: studentMarks) {
if(rollNoToMarksMap.containsKey(marks.getRollNum())) {
rollNoToMarksMap.get(marks.getRollNum()).add(marks);
} else {
rollNoToMarksMap.put(marks.getRollNum(), new ArrayList<>(Arrays.asList(marks)));
}
}
studentMarks.clear();
// Loop over the map entries to see if the student has marks in any one subject greater than the threshold
for(Map.Entry<Integer, List<StudentMarks>> studentEntry : rollNoToMarksMap.entrySet()) {
List<StudentMarks> studentMark = studentEntry.getValue();
Boolean greaterThanThreshold =
studentMark.stream().anyMatch(
mark -> metadataList.stream()
.anyMatch(
metadata -> metadata.getSubject().equals(mark.getSubject()) && mark.getMarks() > metadata.getMarksThreshold()));
if(greaterThanThreshold) {
System.out.println("Roll Number " + studentEntry.getKey());
studentMarks.addAll(studentMark);
}
}
System.out.println("Total Entries in the list " + studentMarks.size());
}
private List<StudentMarks> buildStudentMarks() {
List<StudentMarks> studentMarks = new ArrayList<>();
StudentMarks firstStudMath = StudentMarks.builder().subject("MATH").marks(93).rollNum(1).build();
StudentMarks firstStudEng = StudentMarks.builder().subject("ENG").marks(45).rollNum(1).build();
StudentMarks firstStudPE = StudentMarks.builder().subject("PE").marks(80).rollNum(1).build();
StudentMarks secondStudMath = StudentMarks.builder().subject("MATH").marks(74).rollNum(2).build();
StudentMarks secondStudEng = StudentMarks.builder().subject("ENG").marks(59).rollNum(2).build();
StudentMarks secondStudPE = StudentMarks.builder().subject("PE").marks(88).rollNum(2).build();
StudentMarks thirdStudMath = StudentMarks.builder().subject("MATH").marks(50).rollNum(3).build();
StudentMarks thirdStudEng = StudentMarks.builder().subject("ENG").marks(50).rollNum(3).build();
StudentMarks thirdStudPE = StudentMarks.builder().subject("PE").marks(50).rollNum(3).build();
studentMarks.add(firstStudMath);
studentMarks.add(firstStudEng);
studentMarks.add(firstStudPE);
studentMarks.add(secondStudMath);
studentMarks.add(secondStudEng);
studentMarks.add(secondStudPE);
studentMarks.add(thirdStudMath);
studentMarks.add(thirdStudEng);
studentMarks.add(thirdStudPE);
return studentMarks;
}
private List<Metadata> buildMetadata() {
List<Metadata> metadataList = new ArrayList<>();
Metadata math = Metadata.builder().subject("MATH").marksThreshold(75).build();
Metadata english = Metadata.builder().subject("ENG").marksThreshold(60).build();
Metadata pe = Metadata.builder().subject("PE").marksThreshold(80).build();
// more subjects
metadataList.add(math);
metadataList.add(english);
metadataList.add(pe);
return metadataList;
}
}
#Getter #Builder
class StudentMarks {
Integer rollNum;
String subject;
Integer marks;
}
#Getter #Builder
class Metadata {
String subject;
Integer marksThreshold;
}

Related

Migrating Hibernate Search 5.11.11.Final to any Hibernate Search 6

Is there any way to avoid usage of BooleanJunction while migrating to Hibernate 6?
My (poor) understanding is that Hibernate 6 abandoned Quer DSL and is using JPA criteria instead. Code sample is just about 10 % of code that I have to cover all search cases and it uses more that shown here, meaning BooleanJunction is needed for this to work.
import javax.persistence.EntityManager;
import org.hibernate.search.jpa.FullTextEntityManager;
import org.hibernate.search.SearchFactory;
import org.hibernate.search.query.dsl.QueryContextBuilder;
import org.hibernate.search.query.dsl.EntityContext;
import org.hibernate.search.query.dsl.QueryBuilder;
// ...
#Autowired
private final EntityManager entityManager;
private static final Pattern SPACES =
Pattern.compile("\\s+", Pattern.UNICODE_CHARACTER_CLASS);
private static final int MIN_TOKEN_LENGTH = 2;
#AllArgsConstructor #Getter
public enum SearchField {
// there are 20 entries; here is one just to show
NAME(SearchField.NAME_STR, 20.0f);
private String fieldName;
private float boostFactor;
}
// ...
final FullTextEntityManager fullTextEntityManager =
Search.getFullTextEntityManager(this.entityManager);
final SearchFactory searchFactory =
fullTextEntityManager.getSearchFactory();
final QueryContextBuilder queryContextBuilder =
searchFactory.buildQueryBuilder();
final EntityContext entityContext =
queryContextBuilder.forEntity(clazz);
final QueryBuilder queryBuilder =
entityContext.get();
// ...
final String searchTerm = "some search term";
final String[] searchTerms =
Arrays.stream(SPACES.split(searchTerm))
.map(StringUtils::trimToNull)
.filter(Objects::nonNull)
.filter(s -> s.length() >= IndexingUtil.MIN_TOKEN_LENGTH)
.toArray(String[]::new);
final List<Query> queries = new LinkedList<>();
for (int i = 0; i < searchTerms.length; ++i) {
final String word = searchTerms[i];
queries.add(
queryBuilder
.bool()
.should(
QueryForField.q(
SearchField.NAME,
word,
queryBuilder
)
)
.boostedTo(SearchField.NAME.getBoostFactor()
);
);
}
// more queries are added, not only query by `name`
final BooleanJunction<?> master = queryBuilder.bool();
queries.stream().filter(Objects::nonNull).forEach(master::must);
final Query q = master.createQuery();

A custom Codec or PojoCodec may need to be explicitly configured and registered to handle this type

UserProfileModel has two embedded models. I am giving here concisely:
#Document(collection = "USER_PROFILE")
public class UserProfileModel extends BaseModel<UserModel> {
public static final String FIELD_USER_ID = "userId";
public static final String FIELD_BASIC_INFO = "basicInfo";
public static final String FIELD_CONTACT_INFO = "contactInfo";
private String userId;
private BasicInfo basicInfo;
private ContactInfo contactInfo;
}
public class BasicInfo {
private String gender;
private LocalDate birthDate;
private String[] langs;
private String religiousView;
private String politicalView;
}
public class ContactInfo {
private String[] mobilePhones;
private Address address;
private SocialLink[] socialLinks;
private String[] websites;
private String[] emails;
}
Writing converter class of UserProfileModel:
#Component
#WritingConverter
public class UserProfileModelConverter implements Converter<UserProfileModel, Document> {
#Override
public Document convert(UserProfileModel s) {
Document doc = new Document();
if (null != s.getUserId())
doc.put(UserProfileModel.FIELD_USER_ID, s.getUserId());
if (null != s.getBasicInfo())
doc.put(UserProfileModel.FIELD_BASIC_INFO, s.getBasicInfo());
if (null != s.getContactInfo())
doc.put(UserProfileModel.FIELD_CONTACT_INFO, s.getContactInfo());
}
}
Trying to save an object of UserProfileModel like this:
#Autowired
private UserProfileRepository repo;
UserProfileModel profileModel = generateUserProfileModel();
repo.save(profileModel);
Exception:
org.bson.codecs.configuration.CodecConfigurationException: An exception occurred when encoding using the AutomaticPojoCodec.
Encoding a BasicInfo: 'BasicInfo(gender=Male, birthDate=2020-05-05, langs=[Lang 1, Lang 2], religiousView=Islam (Sunni), politicalView=N/A)' failed with the following exception:
Failed to encode 'BasicInfo'. Encoding 'langs' errored with: Can't find a codec for class [Ljava.lang.String;.
A custom Codec or PojoCodec may need to be explicitly configured and registered to handle this type.
at org.bson.codecs.pojo.AutomaticPojoCodec.encode(AutomaticPojoCodec.java:53)
at org.bson.codecs.EncoderContext.encodeWithChildContext(EncoderContext.java:91)
at org.bson.codecs.DocumentCodec.writeValue(DocumentCodec.java:185)
at org.bson.codecs.DocumentCodec.writeMap(DocumentCodec.java:199)
at org.bson.codecs.DocumentCodec.encode(DocumentCodec.java:141)
at org.bson.codecs.DocumentCodec.encode(DocumentCodec.java:45)
at org.bson.codecs.BsonDocumentWrapperCodec.encode(BsonDocumentWrapperCodec.java:63)
at org.bson.codecs.BsonDocumentWrapperCodec.encode(BsonDocumentWrapperCodec.java:29)
If I don't use converter for UserProfileModel by adding in Mongo Config, then this exception doesn't appear and everything works well.
But I am trying to use the Converter class for some reason.
So is it something wrong or modification needed in converter class?
A Jira ticket already raised on this issue here.
Can't find a codec for class [Ljava.lang.String;
And their reply
The Document class currently supports only List, not native Java
array, so just replace with:
List codes = Arrays.asList("1112233", "2223344");
So, String array is not supported. Use List<String> instead of String[] in models.
Two ways to solve this issue:
Either change String[] to List<String> as mentioned here
Write your custom codec (if you do not have control over a class)
Following is solution 2:
Create a custom Code StringArrayCodec
Register it along with the existing codec
Start/restart application which uses mongo-java driver
Following is Kotlin code but can be converted to java as well.
StringArrayCodec.kt
import java.util.*
import org.bson.BsonReader
import org.bson.BsonType
import org.bson.BsonWriter
import org.bson.codecs.Codec
import org.bson.codecs.DecoderContext
import org.bson.codecs.EncoderContext
class StringArrayCodec : Codec<Array<String>> {
/**
* Encode an instance of the type parameter `T` into a BSON value.
* #param writer the BSON writer to encode into
* #param value the value to encode
* #param encoderContext the encoder context
*/
override fun encode(writer: BsonWriter?, value: Array<String>?, encoderContext: EncoderContext?) {
writer?.writeStartArray()
val isNonNull = value != null
if (isNonNull) {
writer?.writeBoolean(isNonNull)
value?.size?.let { writer?.writeInt32(it) }
for (i in value!!) {
writeValue(writer, i, encoderContext)
}
} else {
writer?.writeBoolean(!isNonNull)
}
writer?.writeEndArray()
}
private fun writeValue(writer: BsonWriter?, s: String, encoderContext: EncoderContext?) {
if (s == null) {
writer?.writeNull()
} else {
writer?.writeString(s)
}
}
/**
* Returns the Class instance that this encodes. This is necessary because Java does not reify generic types.
*
* #return the Class instance that this encodes.
*/
override fun getEncoderClass(): Class<Array<String>>? {
return Array<String>::class.java
}
/**
* Decodes a BSON value from the given reader into an instance of the type parameter `T`.
*
* #param reader the BSON reader
* #param decoderContext the decoder context
* #return an instance of the type parameter `T`.
*/
override fun decode(reader: BsonReader?, decoderContext: DecoderContext?): Array<String>? {
reader?.readStartArray()
val isNonNull = reader?.readBoolean()
val tempArray: Array<String?>?
if (isNonNull == true) {
val size = reader.readInt32()
tempArray = arrayOfNulls(size)
for (i in 0 until size) {
tempArray[i] = readValue(reader, decoderContext)
}
} else {
tempArray = null
}
val array: Array<String>? = if (isNonNull == true) {
Arrays.stream(tempArray)
.filter { s ->
s != null
}
.toArray() as Array<String>
} else {
null
}
reader?.readEndArray()
return array
}
private fun readValue(reader: BsonReader, decoderContext: DecoderContext?): String? {
val bsonType: BsonType = reader.currentBsonType
return if (bsonType == BsonType.NULL) {
reader.readNull()
null
} else {
reader.readString()
}
}
}
Register custom codec as CodecRegistries.fromCodecs(StringArrayCodec())
MongodbConfig.kt
import com.mongodb.ConnectionString
import com.mongodb.MongoClientSettings
import com.mongodb.client.MongoClient
import com.mongodb.client.MongoClients
import com.mongodb.client.MongoCollection
import com.mongodb.client.MongoDatabase
import org.bson.Document
import org.bson.codecs.configuration.CodecRegistries
import org.bson.codecs.configuration.CodecRegistries.fromProviders
import org.bson.codecs.configuration.CodecRegistries.fromRegistries
import org.bson.codecs.configuration.CodecRegistry
import org.bson.codecs.pojo.PojoCodecProvider
import org.springframework.beans.factory.annotation.Value
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.context.annotation.Bean
import org.springframework.stereotype.Component
#EnableConfigurationProperties
#ConfigurationProperties(prefix = "mongodb")
#ConditionalOnProperty(name = ["mongodb.enable"], havingValue = "true", matchIfMissing = false)
#Component
class MongodbConfig
(
#Value("\${mongodb.uri}")
val connectionUri: String,
#Value("\${mongodb.database}")
val database: String,
#Value("\${mongodb.collection-name}")
val collectionName: String
) {
#Bean
fun mongoClient(): MongoClient {
return MongoClients.create(mongodbSettings())
}
#Bean
fun mongoDatabase(mongoClient: MongoClient): MongoDatabase {
return mongoClient.getDatabase(database)
}
#Bean
fun mongodbCollection(mongoDatabase: MongoDatabase): MongoCollection<Document> {
return mongoDatabase.getCollection(collectionName)
}
fun mongodbSettings(): MongoClientSettings {
val pojoCodecRegistry: CodecRegistry = fromRegistries(
CodecRegistries.fromCodecs(StringArrayCodec()), // <---- this is the custom codec
MongoClientSettings.getDefaultCodecRegistry(),
fromProviders(PojoCodecProvider.builder().automatic(true).build())
)
val connectionString = ConnectionString(connectionUri)
return MongoClientSettings.builder()
.codecRegistry(pojoCodecRegistry)
.applyConnectionString(connectionString)
.build()
}
}
Dependency and driver version
implementation("org.mongodb:mongodb-driver-sync:4.2.0") {
because("To connect mongodb instance")
}
The answer from dkb re StringArrayCodec was helpful to me.
If you wish to avoid kotlin and use java instead then here is a non-kotlin-spam-java version:
import org.bson.BsonReader;
import org.bson.BsonWriter;
import org.bson.codecs.Codec;
import org.bson.codecs.DecoderContext;
import org.bson.codecs.EncoderContext;
public final class MongoCodecStringArray implements Codec {
public void encode(BsonWriter writer, String[] value, EncoderContext encoderContext) {
if (writer == null)
return;
writer.writeStartArray();
boolean isNonNull = value != null;
writer.writeBoolean(isNonNull);
if (isNonNull) {
writer.writeInt32(value.length);
for (int i = 0; i < value.length; ++i) {
writer.writeString(value[i]);
}
}
writer.writeEndArray();
}
public void encode(BsonWriter var1, Object var2, EncoderContext var3) {
this.encode(var1, (String[]) var2, var3);
}
public Class getEncoderClass() {
return String[].class;
}
public String[] decodeImpl(BsonReader reader, DecoderContext decoderContext) {
if (reader == null)
return null;
reader.readStartArray();
Boolean isNonNull = reader.readBoolean();
String[] ret = null;
if (isNonNull) {
int size = reader.readInt32();
ret = new String[size];
for (int i = 0; i < size; ++i) {
ret[i] = reader.readString();
}
}
reader.readEndArray();
return ret;
}
public Object decode(BsonReader var1, DecoderContext var2) {
return decodeImpl(var1, var2);
}
}
.

NullPointerException when i try to add a object from a jpa repository to a list

I want to get all projects, where person is empty. Every time i call the endpoint a get a NullPointerException for this line emptyProjects.add(project);. Maybe its because of the JsonBackReference?
And if so, is there any way to get the list of projects where the person list is empty?
Project Class:
package com.example.api.model;
import com.fasterxml.jackson.annotation.JsonBackReference;
import com.fasterxml.jackson.annotation.JsonIdentityInfo;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonManagedReference;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
#Entity
#Table(name = "project")
public class Project {
#Id #GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotNull
private String name;
#ManyToMany(mappedBy = "projectList")
#JsonBackReference
private List<Person> person;
public Project(){}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<Person> getPerson() {
return person;
}
public void setPerson(List<Person> person) {
this.person = person;
}
}
Method iam working on:
#GetMapping("/project/empty")
public ResponseEntity getEmptyProjects(){
List <Project> emptyProjects = null;
for (int i = 0; i <= projectRepo.findAll().size(); i++){
Project project = projectRepo.findAll().get(i);
emptyProjects.add(project);
if (project.getPerson().isEmpty() == true){
emptyProjects.add(project);
}
}
return ResponseEntity.ok(emptyProjects);
}
Thanks!
You're not initializing the list, so you're having a NullPointerException because you're trying to call a method on a null object. Replace this line:
List <Project> emptyProjects = null;
with this
List <Project> emptyProjects = new ArrayList<>();
Moreover, there is no need of calling the database for each iteration of the loop. It'd be better to save the result in a list List<Project> allProjects = projectRepo.findAll() and loop through this list for better performance
Your list is declared as null means no memory address is assigned for that emptyProjects, so when accessing that, you are getting NPE error. Moreover, in your loop condition and accessing the elements of findAll() resultSet, you are unnecessarily calling that method multiple times which is not ok and your condition checking for (int i = 0; i <= projectRepo.findAll().size(); i++){ is wrong. check corrected code:
#GetMapping("/project/empty")
public ResponseEntity getEmptyProjects(){
List <Project> emptyProjects = new ArrayList<>();
List <Project> resultSet=projectRepo.findAll();
for (int i = 0; i < resultSet.size(); i++){
Project project = resultSet.get(i);
emptyProjects.add(project);
if (project.getPerson().isEmpty() == true){
emptyProjects.add(project);
}
}
return ResponseEntity.ok(emptyProjects);
}
As mentioned by #Germano Mosconi you will have to initialize emptyProjects.
// Fetch all projects
List<Project> allProjects = projectRepo.findAll();
// Initialize empty project list here
List<Project> emptyProjects = new ArrayList<>();
// Filter projects having empty person.
for (Project project : allProjects) {
if (null == project.getPerson()) {
emptyProjects.add(project);
}
}

Performance problem when query a many-to-one relation by jpa

I use spring-boot-data-jpa-2.0 to get data from db. A table has many-to-one relation, and the query speed is too slow, 1000 lines data with foreign key will cost 15s, but by native sql it will cost only 0.07s. I search the issue and found that it is because 1+n problem.
Some solution that says use 'join fetch' in hql can solve. When I use the 'join fetch' in hql, query speed not change.
The system designed as a pure rest service, with spring boot framework.
contract entity
import java.time.LocalDateTime;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
#Entity
#Table(name = "MA_CONTRACTINFO_B")
public class MaContractinfoB implements java.io.Serializable {
// Fields
private String contractcd;
private MaDepartmentB maDepartmentB;
private Double contractid;
private String contractnm;
private String partya;
private String deputycontract;
private String prjtype;
private Double fundt;
private String bustype;
private String contractstatus;
private Double contractyear;
private String fundratio;
private LocalDateTime signdate;
private String prj;
private LocalDateTime loaddate;
private String udep;
private Double fundaccont;
private Double receipt;
private Double fundacctot;
private Double receiptot;
private String loc;
private String riskasscd;
private String rm;
private String pm;
private Double fundaccrec;
private String adminleader;
private String techleader;
private String leader;
private String progress;
private String cashadmin;
private String timetask;
private String contracttp;
// Constructors
/** default constructor */
public MaContractinfoB() {
}
/** minimal constructor */
public MaContractinfoB(String contractcd) {
this.contractcd = contractcd;
}
/** full constructor */
public MaContractinfoB(String contractcd, MaDepartmentB maDepartmentB, Double contractid, String contractnm,
String partya, String deputycontract, String prjtype, Double fundt, String bustype, String contractstatus,
Double contractyear, String fundratio, LocalDateTime signdate, String prj, LocalDateTime loaddate,
String udep, Double fundaccont, Double receipt, Double fundacctot, Double receiptot, String loc,
String riskasscd, String rm, String pm, Double fundaccrec, String adminleader, String techleader,
String leader, String progress, String cashadmin, String timetask, String contracttp) {
this.contractcd = contractcd;
this.maDepartmentB = maDepartmentB;
this.contractid = contractid;
this.contractnm = contractnm;
this.partya = partya;
this.deputycontract = deputycontract;
this.prjtype = prjtype;
this.fundt = fundt;
this.bustype = bustype;
this.contractstatus = contractstatus;
this.contractyear = contractyear;
this.fundratio = fundratio;
this.signdate = signdate;
this.prj = prj;
this.loaddate = loaddate;
this.udep = udep;
this.fundaccont = fundaccont;
this.receipt = receipt;
this.fundacctot = fundacctot;
this.receiptot = receiptot;
this.loc = loc;
this.riskasscd = riskasscd;
this.rm = rm;
this.pm = pm;
this.fundaccrec = fundaccrec;
this.adminleader = adminleader;
this.techleader = techleader;
this.leader = leader;
this.progress = progress;
this.cashadmin = cashadmin;
this.timetask = timetask;
this.contracttp = contracttp;
}
// Property accessors
#Id
#Column(name = "CONTRACTCD", unique = true, nullable = false)
public String getContractcd() {
return this.contractcd;
}
public void setContractcd(String contractcd) {
this.contractcd = contractcd;
}
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "DEPID")
public MaDepartmentB getMaDepartmentB() {
return this.maDepartmentB;
}
public void setMaDepartmentB(MaDepartmentB maDepartmentB) {
this.maDepartmentB = maDepartmentB;
}
#Column(name = "CONTRACTID", precision = 38, scale = 8)
public Double getContractid() {
return this.contractid;
}
public void setContractid(Double contractid) {
this.contractid = contractid;
}
#Column(name = "CONTRACTNM")
public String getContractnm() {
return this.contractnm;
}
public void setContractnm(String contractnm) {
this.contractnm = contractnm;
}
#Column(name = "PARTYA")
public String getPartya() {
return this.partya;
}
public void setPartya(String partya) {
this.partya = partya;
}
#Column(name = "DEPUTYCONTRACT")
public String getDeputycontract() {
return this.deputycontract;
}
public void setDeputycontract(String deputycontract) {
this.deputycontract = deputycontract;
}
#Column(name = "PRJTYPE")
public String getPrjtype() {
return this.prjtype;
}
public void setPrjtype(String prjtype) {
this.prjtype = prjtype;
}
#Column(name = "FUNDT", precision = 38, scale = 8)
public Double getFundt() {
return this.fundt;
}
public void setFundt(Double fundt) {
this.fundt = fundt;
}
#Column(name = "BUSTYPE")
public String getBustype() {
return this.bustype;
}
public void setBustype(String bustype) {
this.bustype = bustype;
}
#Column(name = "CONTRACTSTATUS")
public String getContractstatus() {
return this.contractstatus;
}
public void setContractstatus(String contractstatus) {
this.contractstatus = contractstatus;
}
#Column(name = "CONTRACTYEAR", precision = 38, scale = 8)
public Double getContractyear() {
return this.contractyear;
}
public void setContractyear(Double contractyear) {
this.contractyear = contractyear;
}
#Column(name = "FUNDRATIO")
public String getFundratio() {
return this.fundratio;
}
public void setFundratio(String fundratio) {
this.fundratio = fundratio;
}
#Column(name = "SIGNDATE", length = 11)
public LocalDateTime getSigndate() {
return this.signdate;
}
public void setSigndate(LocalDateTime signdate) {
this.signdate = signdate;
}
#Column(name = "PRJ")
public String getPrj() {
return this.prj;
}
public void setPrj(String prj) {
this.prj = prj;
}
#Column(name = "LOADDATE", length = 11)
public LocalDateTime getLoaddate() {
return this.loaddate;
}
public void setLoaddate(LocalDateTime loaddate) {
this.loaddate = loaddate;
}
#Column(name = "UDEP")
public String getUdep() {
return this.udep;
}
public void setUdep(String udep) {
this.udep = udep;
}
#Column(name = "FUNDACCONT", precision = 38, scale = 8)
public Double getFundaccont() {
return this.fundaccont;
}
public void setFundaccont(Double fundaccont) {
this.fundaccont = fundaccont;
}
#Column(name = "RECEIPT", precision = 38, scale = 8)
public Double getReceipt() {
return this.receipt;
}
public void setReceipt(Double receipt) {
this.receipt = receipt;
}
#Column(name = "FUNDACCTOT", precision = 38, scale = 8)
public Double getFundacctot() {
return this.fundacctot;
}
public void setFundacctot(Double fundacctot) {
this.fundacctot = fundacctot;
}
#Column(name = "RECEIPTOT", precision = 38, scale = 8)
public Double getReceiptot() {
return this.receiptot;
}
public void setReceiptot(Double receiptot) {
this.receiptot = receiptot;
}
#Column(name = "LOC")
public String getLoc() {
return this.loc;
}
public void setLoc(String loc) {
this.loc = loc;
}
#Column(name = "RISKASSCD")
public String getRiskasscd() {
return this.riskasscd;
}
public void setRiskasscd(String riskasscd) {
this.riskasscd = riskasscd;
}
#Column(name = "RM")
public String getRm() {
return this.rm;
}
public void setRm(String rm) {
this.rm = rm;
}
#Column(name = "PM")
public String getPm() {
return this.pm;
}
public void setPm(String pm) {
this.pm = pm;
}
#Column(name = "FUNDACCREC", precision = 38, scale = 8)
public Double getFundaccrec() {
return this.fundaccrec;
}
public void setFundaccrec(Double fundaccrec) {
this.fundaccrec = fundaccrec;
}
#Column(name = "ADMINLEADER")
public String getAdminleader() {
return this.adminleader;
}
public void setAdminleader(String adminleader) {
this.adminleader = adminleader;
}
#Column(name = "TECHLEADER")
public String getTechleader() {
return this.techleader;
}
public void setTechleader(String techleader) {
this.techleader = techleader;
}
#Column(name = "LEADER", length = 20)
public String getLeader() {
return this.leader;
}
public void setLeader(String leader) {
this.leader = leader;
}
#Column(name = "PROGRESS", length = 1000)
public String getProgress() {
return this.progress;
}
public void setProgress(String progress) {
this.progress = progress;
}
#Column(name = "CASHADMIN", length = 20)
public String getCashadmin() {
return this.cashadmin;
}
public void setCashadmin(String cashadmin) {
this.cashadmin = cashadmin;
}
#Column(name = "TIMETASK", length = 2000)
public String getTimetask() {
return this.timetask;
}
public void setTimetask(String timetask) {
this.timetask = timetask;
}
#Column(name = "CONTRACTTP", length = 50)
public String getContracttp() {
return this.contracttp;
}
public void setContracttp(String contracttp) {
this.contracttp = contracttp;
}
/**
* toString
*
* #return String
*/
#Override
public String toString() {
StringBuffer buffer = new StringBuffer();
buffer.append(getClass().getName()).append("#").append(Integer.toHexString(hashCode())).append(" [");
buffer.append("contractcd").append("='").append(getContractcd()).append("' ");
buffer.append("maDepartmentB").append("='").append(getMaDepartmentB()).append("' ");
buffer.append("contractid").append("='").append(getContractid()).append("' ");
buffer.append("contractnm").append("='").append(getContractnm()).append("' ");
buffer.append("partya").append("='").append(getPartya()).append("' ");
buffer.append("deputycontract").append("='").append(getDeputycontract()).append("' ");
buffer.append("prjtype").append("='").append(getPrjtype()).append("' ");
buffer.append("fundt").append("='").append(getFundt()).append("' ");
buffer.append("bustype").append("='").append(getBustype()).append("' ");
buffer.append("contractstatus").append("='").append(getContractstatus()).append("' ");
buffer.append("contractyear").append("='").append(getContractyear()).append("' ");
buffer.append("fundratio").append("='").append(getFundratio()).append("' ");
buffer.append("signdate").append("='").append(getSigndate()).append("' ");
buffer.append("prj").append("='").append(getPrj()).append("' ");
buffer.append("loaddate").append("='").append(getLoaddate()).append("' ");
buffer.append("udep").append("='").append(getUdep()).append("' ");
buffer.append("fundaccont").append("='").append(getFundaccont()).append("' ");
buffer.append("receipt").append("='").append(getReceipt()).append("' ");
buffer.append("fundacctot").append("='").append(getFundacctot()).append("' ");
buffer.append("receiptot").append("='").append(getReceiptot()).append("' ");
buffer.append("loc").append("='").append(getLoc()).append("' ");
buffer.append("riskasscd").append("='").append(getRiskasscd()).append("' ");
buffer.append("rm").append("='").append(getRm()).append("' ");
buffer.append("pm").append("='").append(getPm()).append("' ");
buffer.append("fundaccrec").append("='").append(getFundaccrec()).append("' ");
buffer.append("adminleader").append("='").append(getAdminleader()).append("' ");
buffer.append("techleader").append("='").append(getTechleader()).append("' ");
buffer.append("leader").append("='").append(getLeader()).append("' ");
buffer.append("progress").append("='").append(getProgress()).append("' ");
buffer.append("cashadmin").append("='").append(getCashadmin()).append("' ");
buffer.append("timetask").append("='").append(getTimetask()).append("' ");
buffer.append("contracttp").append("='").append(getContracttp()).append("' ");
buffer.append("]");
return buffer.toString();
}
}
department entity
import java.util.HashSet;
import java.util.Set;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Table;
#Entity
#Table(name = "MA_DEPARTMENT_B")
public class MaDepartmentB implements java.io.Serializable {
// Fields
private Long id;
private String name;
private String leader;
private Set<MaContractinfoB> maContractinfoBs = new HashSet<MaContractinfoB>(0);
private Set<MaContraimB> maContraimBs = new HashSet<MaContraimB>(0);
// Constructors
/** default constructor */
public MaDepartmentB() {
}
/** minimal constructor */
public MaDepartmentB(Long id) {
this.id = id;
}
/** full constructor */
public MaDepartmentB(Long id, String name, String leader, Set<MaContractinfoB> maContractinfoBs,
Set<MaContraimB> maContraimBs) {
this.id = id;
this.name = name;
this.leader = leader;
this.maContractinfoBs = maContractinfoBs;
this.maContraimBs = maContraimBs;
}
// Property accessors
#Id
#Column(name = "ID", unique = true, nullable = false, precision = 10, scale = 0)
public Long getId() {
return this.id;
}
public void setId(Long id) {
this.id = id;
}
#Column(name = "NAME")
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
#Column(name = "LEADER")
public String getLeader() {
return this.leader;
}
public void setLeader(String leader) {
this.leader = leader;
}
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "maDepartmentB")
public Set<MaContractinfoB> getMaContractinfoBs() {
return this.maContractinfoBs;
}
public void setMaContractinfoBs(Set<MaContractinfoB> maContractinfoBs) {
this.maContractinfoBs = maContractinfoBs;
}
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "maDepartmentB")
public Set<MaContraimB> getMaContraimBs() {
return this.maContraimBs;
}
public void setMaContraimBs(Set<MaContraimB> maContraimBs) {
this.maContraimBs = maContraimBs;
}
/**
* toString
*
* #return String
*/
#Override
public String toString() {
StringBuffer buffer = new StringBuffer();
buffer.append(getClass().getName()).append("#").append(Integer.toHexString(hashCode())).append(" [");
buffer.append("id").append("='").append(getId()).append("' ");
buffer.append("name").append("='").append(getName()).append("' ");
buffer.append("leader").append("='").append(getLeader()).append("' ");
buffer.append("maContractinfoBs").append("='").append(getMaContractinfoBs()).append("' ");
buffer.append("maContraimBs").append("='").append(getMaContraimBs()).append("' ");
buffer.append("]");
return buffer.toString();
}
}
jparepository
public interface MaContractinfoBRepository extends JpaRepository<MaContractinfoB, Long> {
#Query("from MaContractinfoB c join fetch c.maDepartmentB")
List<MaContractinfoB> findAll();
#Query("select contractnm from MaContractinfoB")
List<String> findAllName();
// #Query("from MaContractinfoB c join fetch c.maDepartmentB")
// List test();
}
In MaContractinfoBRepository, When I use findAllName function, it will immediately return 1000 contract names in 0.05s. When I use findAll, it will cost 15s to get 1000 data with department entity, even I add join fetch. But if I get it by native sql in db tool such as navicat, it will cost only 0.07s.
Is any keypoint I missed? How to query the MaContractinfoB table not so slowly?
This is happening because internally with 'fetch' command also hibernate is lazy loading all the rows of Department entity for each Contract id whenever you call the findAll() method. So if there are n rows in Department for 1 Contract and in total there are m Contracts then the total number of calls to the database would be 'm * n'.
One way around this is by using DTO projections. Data Transfer Objects is an easy way to define all the required columns in one query and hit the database for once.
I have found this article useful which shows multiple ways of writing DTO projections.
https://thoughts-on-java.org/dto-projections/
One of the mentioned way is: Using JPQL you can use a constructor expression to define a constructor call with the keyword new followed by the fully classified class name of your DTO and a list of constructor parameters in curly braces.
Something like this:
TypedQuery<ContractWithDepartmentDetails> q = em.createQuery(
"SELECT new com.practice.model.ContractWithDepartmentDetails(c.id, c.name, d.name) FROM contract c JOIN c.department d",
ContractWithDepartmentDetails.class);

Wrap collections in Value Objects

I have the following entities:
public class ComplexEntity {
public List<TenderLocation> tenderList;
public ComplexEntity(List<TenderLocation> tenderList) {
this.tenderList = tenderList;
}
}
public class TenderLocation {
public String location;
public List<TenderAirline> tenderAirlines;
public TenderLocation(String location, List<TenderAirline> tenderAirlines) {
this.tenderAirlines = tenderAirlines;
this.location = location;
}
}
public class TenderAirline {
public int ID;
public String name;
public TenderAirline(int ID, String name) {
this.ID = ID;
this.name = name;
}
}
And the following test for comparing two ComplexEntiey:
public class ComplexObjectGraphComparisonExample {
#Test
public void shouldCompareTwoComplexObjects() {
// given
Javers javers = JaversBuilder.javers().build();
// Construct test data
// ComplexEntity:
// - List<TLocation>
// TLoation:
// - location: String
// - List<TAir>
// TAir:
// - int ID
// - String Name
int locations = 3;
List<TenderLocation> tenderLocationsBase = new ArrayList<TenderLocation>(locations);
List<TenderLocation> tenderLocationsRef = new ArrayList<TenderLocation>(locations);
for (int j = 0; j < locations; ++j) {
int airlines = 10;
List<TenderAirline> tenderAirlinesBase = new ArrayList<TenderAirline>(airlines);
List<TenderAirline> tenderAirlinesRef = new ArrayList<TenderAirline>(airlines);
for (int i = 0; i < airlines; ++i) {
tenderAirlinesBase.add(new TenderAirline(i, "Airline" + i));
tenderAirlinesRef.add(new TenderAirline(i, "Airline" + i));
}
tenderLocationsBase.add(new TenderLocation("BV" + j, tenderAirlinesBase));
tenderLocationsRef.add(new TenderLocation("BV" + j, tenderAirlinesBase));
}
ComplexEntity baseEntity = new ComplexEntity(tenderLocationsBase);
ComplexEntity referenceEntity = new ComplexEntity(tenderLocationsRef);
// when
Diff diff = javers.compare(baseEntity, referenceEntity);
assertThat(diff.getChanges()).hasSize(0);
// Change a single small thing
referenceEntity.tenderList.get(1).location = "Difference_1";
// then there is a single change detected
diff = javers.compare(baseEntity, referenceEntity);
assertThat(diff.getChanges()).hasSize(1);
// there should be one change of type {#link ValueChange}
ValueChange change = diff.getChangesByType(ValueChange.class).get(0);
assertThat(change.getPropertyName()).isEqualTo("location");
assertThat(change.getLeft()).isEqualTo("BV1");
assertThat(change.getRight()).isEqualTo("Difference_1");
// do another change
referenceEntity.tenderList.get(1).tenderAirlines.get(1).name = "Difference_2";
// second difference is not detected, failing the commented test
diff = javers.compare(baseEntity, referenceEntity);
assertThat(diff.getChanges()).hasSize(2);
System.out.println(diff);
}
}
At comparison my second change is not identified because the compare method is not comparing in depth my lists.
I have read here
http://www.atetric.com/atetric/javadoc/org.javers/javers-core/1.3.4/org/javers/core/Javers.html
that if I "wrap collections in some Value Objects" the deep comparing of the collection is possible.
My question is, How exactly I can wrap my collection into Value Objects?
You can wrap the object something like below:
public class Wrapper
{
private final WrappedObject obj;
public Wrapper (WrappedObject obj)
{
this.obj = obj;
}
}
What is wrong in you code is mapping, you didn't do it at all. You should map your entities as Entities using #Id annotation:
public class TenderLocation {
#Id
public String location;
...
public class TenderAirline {
#Id
public int ID;
public String name;
...
Otherwise, JaVers maps your classes as Value Objects (objects without identity) which gives you limited diff experience.

Resources