I am new in functional programming.
I want to understand which concept am I missing here.
Stream<Employee> stream1 = getEmployeeListOne().stream();
Stream<Employee> stream2 = getEmployeeListTwo().stream();
Predicate<Employee>predicate=x->x.getFirstName().startsWith("L");
//Below line is compile time error
List<Employee>list=stream1.filter((x->x.getFirstName().startsWith("L")).or(x->x.getLastName().startsWith("L"))).collect(Collectors.toList());
//Below line works well
//List<Employee>list2=stream1.filter(predicate.or(x->x.getLastName().startsWith("L"))).collect(Collectors.toList());
It simply says lambda expression not expected here
A Predicate has the or() method.
A lambda does not.
If you assign the lambda to a variable of type Predicate, you’re fulfilling the implementation of Predicate’s single unimplemented method test(), which only then has an or() method.
THis is how i have tried to do it, extract similar test condition in a method , make predicate return boolean of composite condition.
The chaining does work if you write
final Predicate<Employee> onlyEmpNameStarsWithL = employee -> nameStartsWithL(employee.firstName);// || nameStartsWithL(employee.lastName);
and then use full code as below:
List<Employee> allEmployeeWithLInName = employeeStream.filter(Objects::nonNull)
.filter(onlyEmpNameStarsWithL.or(e -> nameStartsWithL(e.lastName)))
.collect(Collectors.toList());
allEmployeeWithLInName.forEach(System.out::println);
Or the complete other approach
final Predicate<Employee> onlyEmpNameStarsWithL =
employee -> nameStartsWithL(employee.firstName) || nameStartsWithL(employee.lastName);
helper method
static boolean nameStartsWithL(String name) {
return name != null && name.startsWith("L");
}
Full source code
package examples;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
class Employee {
String firstName;
String lastName;
public Employee(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
#Override
public String toString() {
return "Employee{" +
"firstName='" + firstName + '\'' +
", lastName='" + lastName + '\'' +
'}';
}
}
public class LambdaPredicate {
static boolean nameStartsWithL(String name) {
return name != null && name.startsWith("L");
}
public static void main(String[] args) {
List<Employee> employeeList = Arrays.asList(
new Employee("A", "P"),
new Employee("B", "L"),
null,
new Employee("C", "Q"),
new Employee("L", "I"),
new Employee("L", "L")
);
final Predicate<Employee> onlyEmpNameStarsWithL = employee -> nameStartsWithL(employee.firstName) || nameStartsWithL(employee.lastName);
Stream<Employee> employeeStream = employeeList.stream();
List<Employee> allEmployeeWithLInName = employeeStream.filter(Objects::nonNull)
.filter(onlyEmpNameStarsWithL)
.collect(Collectors.toList());
allEmployeeWithLInName.forEach(System.out::println);
}
}
Output
Employee{firstName='B', lastName='L'}
Employee{firstName='L', lastName='I'}
Employee{firstName='L', lastName='L'}
Related
I cannot figure out why my converter class is not being called. I have the following Entity class:
import org.springframework.validation.annotation.Validated;
import javax.persistence.Column;
import javax.persistence.Convert;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import java.util.List;
#Entity
#Validated
#Table(name = "c_mark", schema="common")
public class CMark {
#Id
#Column(name = "c_mark_id")
private String id;
#Column(name = "cl_fk")
private String cFk;
#Column(name = "s_con")
#Convert(converter = StringListConverterCommaDelim.class)
private List<String> sCon;
#Override
public String toString() {
return "ClassificationMarking{" +
"id='" + id + '\'' +
", cFk='" + cFk + '\'' +
", s_con='" + s_con + '\'' +
'}';
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getCFk() {
return cFk;
}
public void setCFk(String cFk) {
this.cFk = cFk;
}
public List<String> getSCon() {
return sCon;
}
public void setSCon(List<String> sCon) {
this.sCon = sCon;
}
}
Here is the converter class:
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
import java.util.Arrays;
import java.util.List;
import static java.util.Collections.emptyList;
#Converter
public class StringListConverterCommaDelim implements AttributeConverter<List<String>, String>
{
private static final String SPLIT_CHAR = ",";
#Override
public String convertToDatabaseColumn(List<String> stringList) {
return stringList != null ? String.join(SPLIT_CHAR, stringList) : "";
}
#Override
public List<String> convertToEntityAttribute(String string) {
return string != null ? Arrays.asList(string.split(SPLIT_CHAR)) : emptyList();
}
}
Here is the repo interface that defines the insert method:
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
public interface CMarkRepository extends JpaRepository <ClassificationMarking, String> {
#Transactional
#Modifying
#Query(value = "INSERT INTO c_mark(c_fk, s_con) " +
"values(:c_fk, :s_con)", nativeQuery = true)
int insertCMark(#Param("c_fk") String c_fk, #Param("s_con") String s_con);
}
, and finally the method that invokes the insert:
public int postCMark(CMark cMark) {
CMark cm = null;
int status = 0;
try {
status = cMarkingRepository.insertCMark(cMark.getCFk(), cMark.getSCon());
} catch ( Exception e) {
e.printStackTrace();
}
return status;
}
My expectation is that the conversion takes place from the insertCMark() call? I am not sure. In any event, the converter is never called. I would be grateful for any ideas. Thanks!
You're not inserting the whole Entity. So I guess from Spring perspective you just give normal String parameters and Spring probably doesn't know the parameter should somehow be converted.
Despite that shouldn't you even get a compile error because you try to call insertCMark(String, String) as insertCMark(String, List<String>)?
Right now I would say that there is no need for some fancy Spring magic.
You can just tweak the getSCon() method to return a String and convert it in there. Or when you need it for something else to create a second method getSConString():
public String getSCon() {
return this.sCon != null ? String.join(SPLIT_CHAR, this.sCon) : "";
}
Another way is to use your current Converter by hand when calling insertCMark:
public int postCMark(CMark cMark) {
CMark cm = null;
int status = 0;
AttributeConverter<List<String>, String> converter = new StringListConverterCommaDelim();
String sCon = converter.convertToDatabaseColumn(cMark.getSCon());
try {
status = cMarkingRepository.insertCMark(cMark.getCFk(), sCon);
} catch ( Exception e) {
e.printStackTrace();
}
return status;
}
I am currently working on a Spring Boot Hibernate.
I need to save a list of objects in mySql database,
I read about the difference between saving using saveAll() and foreach save() and I can conclude that saveAll() is much faster. However, in case of exception say UK violation for example, the whole process rollback, and no object will be saved.
To be more simple: I want to save a list of object in one transaction, and in case of any constraint violation, ignore it and save the rest.
this is my entity:
package com.entity;
import org.hibernate.annotations.GenericGenerator;
import org.hibernate.annotations.Parameter;
import org.springframework.transaction.annotation.Transactional;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
#Entity
#Transactional
public class Page {
#Id
#GeneratedValue(generator = "page_sequence-generator")
#GenericGenerator(
name = "page_sequence-generator",
strategy = "org.hibernate.id.enhanced.SequenceStyleGenerator",
parameters = {
#Parameter(name = "sequence_name", value = "page_sequence"),
#Parameter(name = "initial_value", value = "1"),
#Parameter(name = "increment_size", value = "1")
}
)
private long id;
#Column(nullable = false, unique = true)
private String url;
private boolean isPageConsumed;
public Page() {
}
public Page(String url, boolean isPageConsumed) {
this.url = url;
this.isPageConsumed = isPageConsumed;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public boolean isPageConsumed() {
return isPageConsumed;
}
public void setPageConsumed(boolean pageConsumed) {
isPageConsumed = pageConsumed;
}
#Override
public String toString() {
return "Page{" +
"id=" + id +
", url='" + url + '\'' +
", isPageConsumed=" + isPageConsumed +
'}';
}
}
And this is my repository:
#Repository
public interface PageRepository extends CrudRepository<Page, String> {
#Query(value = "SELECT p FROM Page p where p.isPageConsumed = '0'")
public Page findFirstPage();
}
Thank you :)
You could use Spring's #Transactional(noRolbackFor={SomeException.class}) on the controller method that invokes saveAll().
I used SpringBoot, and in the PUT method I check if the score exists then I want to update the score and also update the history by adding the latest score to it.
The Score Class:
package thesisMongoProject;
import java.util.Date;
import java.util.List;
import javax.validation.constraints.NotBlank;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import com.fasterxml.jackson.annotation.JsonView;
#Document(collection = "score")
public class Score {
#Id
#NotBlank
#JsonView(Views.class)
private String score;
#NotBlank
#JsonView(Views.class)
private String player;
#NotBlank
#JsonView(Views.class)
private String code;
#JsonView(Views.class)
private Date date;
private List<History> history;
public String getScore() {
return score;
}
public void setScore(String score) {
this.score = score;
}
public String getPlayer() {
return player;
}
public void setPlayer(String player) {
this.player = player;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public List<History> getHistory() {
return history;
}
public void setHistory(List<History> history) {
this.history = history;
}
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
#Override
public String toString() {
return "Score [score=" + score + ", player=" + player + ", code=" + code + ", history=" + history + ", date="
+ date + "]";
}
}
The ScoreRepository:
package thesisMongoProject.Repository;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.stereotype.Repository;
import thesisMongoProject.Score;
import thesisMongoProject.ScoreDto;
#Repository
public interface ScoreRepository extends MongoRepository<Score, String>{
public Score findByScore(String score);
public void save(ScoreDto scoredto, String score);
}
But the PUT method save a new instance into the MongoDB instead of updating the existing one
The PUT method:
//Update Score By ID
#PutMapping("/{score}")
public ResponseEntity<?> updatePlayerByID(
#PathVariable("score")String score,
#RequestBody #JsonView(Views.class) #Valid Score score1){
Score findscore = srepo.findByScore(score);
if(findscore == null)
return ResponseEntity.status(404).body("There is not Score!");
else {
history = new ArrayList<History>();
h = new History();
h.setScore(score1.getScore());
h.setDate(score1.getDate());
history.add(h);
score1.setHistory(history);
srepo.save(score1);
return ResponseEntity.ok(score1);
}
}
Also i tried to use ScoreDTO and #PatchMapping like this:
The ScoreDTo Class:
package thesisMongoProject;
import java.util.List;
public class ScoreDto {
private String score;
List<History> history;
public String getScore() {
return score;
}
public void setScore(String score) {
this.score = score;
}
public List<History> getHistory() {
return history;
}
public void setHistory(List<History> history) {
this.history = history;
}
}
And the PATCHMAPPING:
#PatchMapping("/{score}")
public ResponseEntity<?> updateByScore(
#PathVariable("score")String score,
#RequestBody ScoreDto score1){
Score findscore = srepo.findByScore(score);
if(findscore == null)
return ResponseEntity.status(404).body("There is not Score!");
else {
srepo.save(score1, score);
return ResponseEntity.ok(score1);
}
}
but in my console I have an error:
org.springframework.data.mapping.PropertyReferenceException: No property save found for type Score! Did you mean 'date'?
could you help me how can i update the existing field of score, please?!
The primary key of a database should not be mutable. If there are multiple players with the same score, the earlier players' data would be replaced.
Ideally, for updating an existing document where id and all its new fields are known, something like this can be done:
score1.setScore(score);
srepo.save(score1);
Assuming score is the id of the document that is to be updated and score1 contains all other fields correctly, this will replace the existing document with id score with the new one score1.
In the first code ( the PUT method ), score1 should have the same id as findscore, then it will update the existing document.
Score findscore = srepo.findByScore(score);
if(findscore == null)
return ResponseEntity.status(404).body("There is not Score!");
else {
history = new ArrayList<History>();
h = new History();
h.setScore(score1.getScore());
h.setDate(score1.getDate());
history.add(h);
Also, for the exception you are getting, this save method
public void save(ScoreDto scoredto, String score);
can't be handled by the spring data repository automatically, you will have to define its implementation. More on what kind of methods can be defined or not here. The Standard save method in the repository can be used to achieve the required.
I want to actually understand the difference between Propagation.NESTED VS
Propagation.REQUIRES_NEW.. My requirement is to save the updateStudent and updateSchool and to rollback the updateBus method where i made runtime exception purposefully. so i have used Propagation.NESTED and tested, which works fine, but the result is same with or without Propagation.NESTED in updateBus method, so what is the purpose of NESTED here.
On other note when i change my code like this without updateStudentInner method, entire transaction is roll backed. So here i don't understand the difference in keeping the Transactional level in updateStudentInner vs updateStudentDetail
#Transactional(propagation = Propagation.REQUIRES_NEW)
public void updateStudentDetail(String name, int id) {
updateStudent(name,id);
updateSchool(name, id);
updateBus(name, id);
}
Here is the entire code for this
package com.example.dao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
#Service
public class ExampleDaoImpl {
#Autowired
JdbcTemplate jdbcTemplate;
public void updateStudentDetail(String name, int id) {
updateStudentInner(name, id);
}
#Transactional(propagation = Propagation.REQUIRES_NEW)
public void updateStudentInner(String name, int id) {
updateStudent(name, id);
updateSchool(name, id);
updateBus(name, id);
}
public void updateStudent(String name, int id) {
String query = "UPDATE STUDENT SET NAME=? WHERE ID=?";
name = "Name " + name;
int i = jdbcTemplate.update(query, name, id);
System.out.println("i updateStudent-->" + i);
}
public void updateSchool(String name, int id) {
name = "School " + name;
String query = "UPDATE SCHOOL SET NAME=? WHERE ID=?";
int i = jdbcTemplate.update(query, name, id);
System.out.println("i updateSchool-->" + i);
}
#Transactional(propagation = Propagation.NESTED)
public void updateBus(String name, int id) {
name = "Bus " + name;
String query = "UPDATE BUS SET AME=? WHERE ID=?";
int i = jdbcTemplate.update(query, name, id);
System.out.println("i updateBUs-->" + i);
}
}
Also it would be better is someone explain with realtime example of the difference between REQUIRE_NEW, REQUIRED, NESTED and where these to be used.
I am currently playing with Java 8 and I found a problem with Function. I would like ton know if there is a way to use function reference (name::methode) with a Function with tree parameters without declare a new functional interface (i.e. TriFunction).
I tried with currying way, but it doesn't work.
I have three classes :
Person.class
public class Person {
public enum Sex {
MALE, FEMALE
}
private String firstName;
private String lastName;
private Sex gender;
public Person(String firstName, String lastName, Sex gender) {
this.firstName = firstName;
this.lastName = lastName;
this.gender = gender;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
public Sex getGender() {
return gender;
}
}
PersonFactory
public class PersonFactory {
public static Person create(String firstName, String lastName, String gender) {
// Check firstName Parameter
if(firstName == null || firstName.isEmpty()) {
throw new IllegalArgumentException("The firstName argument expect to not be null or empty");
}
// Check lastName Parameter
if(lastName == null || lastName.isEmpty()) {
throw new IllegalArgumentException("The lastName argument expect to not be null or empty");
}
// Check gender Parameter
if(gender == null || gender.isEmpty()) {
throw new IllegalArgumentException("The gender argument expect to not be null or empty");
} else {
switch(gender) {
case "M":
return new Person(firstName, lastName, Sex.MALE);
case "F":
return new Person(firstName, lastName, Sex.FEMALE);
default:
throw new IllegalArgumentException("The gender parameter is supposed to be either 'M' for male or 'F' for Female");
}
}
}
}
CsVPersonParser
public class CsvPersonParser {
public Person parseLine(String line, String separator, Function<String, Function<String, Function<String, Person>>> creator) {
String[] separedLine = line.split(separator);
String firstName = separedLine[0];
String lastName = separedLine[1];
String gender = separedLine[2];
return creator.apply(firstName).apply(lastName).apply(gender);
}
}
Here is my main class :
public class App {
public static void main(String[] args) {
final String IMAGINARY_CSV_FILE_LINE = "Jean,Dupont,M";
CsvPersonParser csvParser = new CsvPersonParser();
csvParser.parseLine("blabla", ",", PersonFactory::create);
}
}
The compilator show : The type PersonFactory does not define create(String) that is applicable here
It seems pretty logical. I have no solution. Is anyone can help me ?
I wonder why there is no way to do it simple without to create new things.
Probably a tri function is quite complex. I suggest that you use a builder to create a person.
The main reasons are, that you are not fixed on parameter ordering and you can extend your person. When you use a trifunction where all parameters are strings its often hard to say which parameter is the first/second/third. And when you want to add an address to a person it becomes more difficult to make it with generic classes like TriFunction.
My suggestion:
public interface PersonBuilder {
PersonBuilder withFirstName(String firstName);
PersonBuilder withLastName(String lastName);
PersonBuilder withGender(String gender);
Person create();
}
Concrete Implementation:
public class DefaultPersonBuilder implements PersonBuilder {
private String firstName;
private String lastName;
private String gender;
#Override
public PersonBuilder withFirstName(String firstName) {
this.firstName = firstName;
return this;
}
#Override
public PersonBuilder withLastName(String lastName) {
this.lastName = lastName;
return this;
}
#Override
public PersonBuilder withGender(String gender) {
this.gender = gender;
return this;
}
#Override
public Person create() {
// Check firstName Parameter
if (firstName == null || firstName.isEmpty()) {
throw new IllegalArgumentException("The firstName argument expect to not be null or empty");
}
[... your implementation using the fields]
}
}
Your parser method:
public Person parseLine(String line, String separator, PersonBuilder person) {
String[] separedLine = line.split(separator);
String firstName = separedLine[0];
String lastName = separedLine[1];
String gender = separedLine[2];
return person.withFirstName(firstName).withLastName(lastName).withGender(gender).create();
}
Now you can change the argument order oder add new fields to person without creating a function with 10 parameters. The parser interface is simpler now, too.
There is no way to do that what I wanted. However two other solutions is possible. Use a lambda instead of PersonFactory::create or create a new functional interface.
Here is the result :
New functional interface
#FunctionalInterface
public interface TriFunction<A, B, C, D> {
public D apply(A a, B b, C c);
}
Add a function parseLine with my new functional interface
public class CsvPersonParser {
// Currying style
public Person parseLine(String line, String separator, Function<String, Function<String, Function<String, Person>>> creator) {
String[] separedLine = line.split(separator);
String firstName = separedLine[0];
String lastName = separedLine[1];
String gender = separedLine[2];
return creator.apply(firstName).apply(lastName).apply(gender);
}
// New Functionnal interface style
public Person parseLine(String line, String separator, TriFunction<String, String, String, Person> creator) {
String[] separedLine = line.split(separator);
String firstName = separedLine[0];
String lastName = separedLine[1];
String gender = separedLine[2];
return creator.apply(firstName, lastName, gender);
}
}
My main class with solutions
public class App {
public static void main(String[] args) {
final String DATA_FILE_SEPARATOR = ",";
final String FAKE_CSV_LINE = "Jean,Dupont,M";
CsvPersonParser csvParser = new CsvPersonParser();
Person person;
// Use curryling style
person = csvParser.parseLine(FAKE_CSV_LINE, DATA_FILE_SEPARATOR, f -> l -> g -> PersonFactory.create(f, l, g));
System.out.println("Currying style : " + person.getFirstName() + " " + person.getLastName() + " " + person.getGender());
// Use new functionnal interface
person = csvParser.parseLine(FAKE_CSV_LINE, DATA_FILE_SEPARATOR, PersonFactory::create);
System.out.println("TriFunction style : " + person.getFirstName() + " " + person.getLastName() + " " + person.getGender());
// Use lambda style
person = csvParser.parseLine(FAKE_CSV_LINE, DATA_FILE_SEPARATOR, (a,b,c) -> PersonFactory.create(a, b, c));
System.out.println("Lambda style : " + person.getFirstName() + " " + person.getLastName() + " " + person.getGender());
}
}