I have the following JPA entity:
#Entity
public class Message {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#NotNull
#ManyToOne(fetch = FetchType.LAZY)
private Member sender;
#NotNull
#ManyToOne(fetch = FetchType.LAZY)
private Member recipient;
From a collection of messages (Collection<Message> messages) I am trying to obtain a Map<Member, List<Message>> messageMap by grouping by either on the recipient OR sender fields.
To further define my use case, the connected member (currentMember) has a number of sent and received messages attached to its instance. I want to retrieve and add the messages to a collection that will contain all of the connected member messages (sent or received) as follows:
Collection<Message> messages = new ArrayList<Message>();
messages.addAll(currentMember.getSentMessages());
messages.addAll(currentMember.getReceivedMessages());
And for each other/opposite member with which the current member has exchanged at least one message, I would like to obtain a list of all exchanged messages.
I will then be able to construct the above Map<Member, List<Message>> messageMap.
Is this possible out of the box with the stream api or do I need to implement my own collector?
If I understand your question correctly, you want to treat the messages uniformly by using the receiver as key for sent messages and using the sender as key for received messages. You could do this by using a conditional in the collect operation:
Map<Member, List<Message>> map = Stream.concat(
currentMember.getSentMessages().stream(), currentMember.getReceivedMessages().stream())
.collect(Collectors.groupingBy(msg ->
msg.getSender()==currentMember? msg.getRecipient(): msg.getSender()));
This uses either, sender or receiver, as the key depending on the result of a comparison with the currentMember, so that the key will always be “the other side”. It may not be the most efficient solution as it evaluates an information which is actually known beforehand, but it’s simple.
If you want to avoid regeneration of the already known information, you have to carry it through the stream. In the absence of a standard Pair type you may use Map.Entry instances to encapsulate pairs of “desired key” and Message instance:
Map<Member, List<Message>> map =
Stream.concat(
currentMember.getSentMessages().stream().map(msg ->
new AbstractMap.SimpleImmutableEntry<>(msg.getRecipient(), msg)),
currentMember.getReceivedMessages().stream().map(msg ->
new AbstractMap.SimpleImmutableEntry<>(msg.getSender(), msg)))
.collect(Collectors.groupingBy(e -> e.getKey(),
Collectors.mapping(e->e.getValue(), Collectors.toList())));
Here, not only the wrapping into the pairs complicates the code, the collection operation’s code also becomes more complicated due to the required unwrapping.
The decision which direction to go is up to you…
Related
I've been struggling with a problem with one of my lists of data because one of the requirements after generating it is to group some of them by some common parameters (more than 1)
What I should get at the end is a map where the value is a list of common objects. For example.
List<Cause> listToGroup = new ArrayList<>();
listToGroup.add(Similar);
listToGroup.add(Common);
listToGroup.add(Similar);
listToGroup.add(Similar);
listToGroup.add(Common);
In a weird way to represent one group (Similar) and the other (Common), those should be separated into two different lists (that list is generated by a request to other methods, in that case, I just added manually to show what could be the contained data in the list). My main problem is the criteria to group them because is based on a group of parameters that are shared, but not all (if the required parameters are equal, should belong to the same list) In the class shown below, that behaviour is seen because there are some parameters that are not being considered.
public class Cause extends GeneralDomain {
//parameters which must be equals between objects
private Long id;
private Date creationDate;
private Part origin;
private Part destination;
//parameters which are not required to be equal
private BigDecimal value
private Stage stageEvent
//omitted getters and setters
}
I've been seeing the comparator method and the groupingBy method provided in Java 8, but at the moment I just know how to perform that task considering just one parameter (for example grouping them by id) And I have no idea about how to group them using more than one parameter.
//this should be the code if the requirement would be just one parameter to groupby, but in my case are more than one.
Map<Long, List<Cause>> result = request.getList(criteria)
.stream()
.map(p -> parsin.createDto(p))
.collect(groupingBy(Cause ::getId));
I would be really glad for any suggestion. If my explanation is not clear, I'm so sorry. That became so complicated that even is hard for me to explain
developping a new Java Spring MVC microservice i have encountered a minor issue.
When i send a creation request for any entity, the id generated always follows the previous one.
My event entity id configuration
My user entity id configuration
For example, this is what i got from these 2 requests
User creation request (you can see the id value is 1)
Event creation request (you can see the id value is 2)
The created event Id is the last created user Id + 1 which i obviously do not want to happen.
I want separate Id values for each entity. I want to know what i am doing wrong.
Thank you
Your solution worker pretty well ;)
I finally used it and added #SequenceGenerator annotation to initialize the count at 0.
#SequenceGenerator(name = "seq", initialValue = 0)
public class ClassName {
#Id
#GeneratedValue(generator = "seq")
private Integer id;
}
Thank you very much Daniel, that's kind of you.
You are using #GeneratedValue without providing a strategy.
Therefore it uses the AUTO strategy by default which indicates that the persistence provider should pick an appropriate strategy for the particular database.
As both ID columns share the same name I assume that both entities share one and the same generator.
Which results in
Create event entity with ID = 1 as the generator started at one
Create user entity with ID = 1 + 1 as new generated value is requested
You should think about using different sequences for generating separate IDs for each entity.
Following uses a DB sequence to generate an ID
#Id
#GeneratedValue(generator = "my_entity_name_seq")
private long id;
If I would not specify a concrete generator in the annotation hibernate ,for example will, create a default sequence called hibernate_sequence which is then used for all entities which use #GeneratedValue without specifying a generator. This then leads to incremented values over all tables / entities.
I have a domain object in Spring which I am saving using JpaRepository.save method and using Sequence generator from Postgres to generate id automatically.
#SequenceGenerator(initialValue = 1, name = "device_metric_gen", sequenceName = "device_metric_seq")
public class DeviceMetric extends BaseTimeModel {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "device_metric_gen")
#Column(nullable = false, updatable = false)
private Long id;
///// extra fields
My use-case requires to do an upsert instead of normal save operation (which I am aware will update if the id is present). I want to update an existing row if a combination of three columns (assume a composite unique) is present or else create a new row.
This is something similar to this:
INSERT INTO customers (name, email)
VALUES
(
'Microsoft',
'hotline#microsoft.com'
)
ON CONFLICT (name)
DO
UPDATE
SET email = EXCLUDED.email || ';' || customers.email;
One way of achieving the same in Spring-data that I can think of is:
Write a custom save operation in the service layer that
Does a get for the three-column and if a row is present
Set the same id in current object and do a repository.save
If no row present, do a normal repository.save
Problem with the above approach is that every insert now does a select and then save which makes two database calls whereas the same can be achieved by postgres insert on conflict feature with just one db call.
Any pointers on how to implement this in Spring Data?
One way is to write a native query insert into values (all fields here). The object in question has around 25 fields so I am looking for an another better way to achieve the same.
As #JBNizet mentioned, you answered your own question by suggesting reading for the data and then updating if found and inserting otherwise. Here's how you could do it using spring data and Optional.
Define a findByField1AndField2AndField3 method on your DeviceMetricRepository.
public interface DeviceMetricRepository extends JpaRepository<DeviceMetric, UUID> {
Optional<DeviceMetric> findByField1AndField2AndField3(String field1, String field2, String field3);
}
Use the repository in a service method.
#RequiredArgsConstructor
public class DeviceMetricService {
private final DeviceMetricRepository repo;
DeviceMetric save(String email, String phoneNumber) {
DeviceMetric deviceMetric = repo.findByField1AndField2AndField3("field1", "field", "field3")
.orElse(new DeviceMetric()); // create new object in a way that makes sense for you
deviceMetric.setEmail(email);
deviceMetric.setPhoneNumber(phoneNumber);
return repo.save(deviceMetric);
}
}
A word of advice on observability:
You mentioned that this is a high throughput use case in your system. Regardless of the approach taken, consider instrumenting timers around this save. This way you can measure the initial performance against any tunings you make in an objective way. Look at this an experiment and be prepared to pivot to other solutions as needed. If you are always reading these three columns together, ensure they are indexed. With these things in place, you may find that reading to determine update/insert is acceptable.
I would recommend using a named query to fetch a row based on your candidate keys. If a row is present, update it, otherwise create a new row. Both of these operations can be done using the save method.
#NamedQuery(name="getCustomerByNameAndEmail", query="select a from Customers a where a.name = :name and a.email = :email");
You can also use the #UniqueColumns() annotation on the entity to make sure that these columns always maintain uniqueness when grouped together.
Optional<Customers> customer = customerRepo.getCustomersByNameAndEmail(name, email);
Implement the above method in your repository. All it will do it call the query and pass the name and email as parameters. Make sure to return an Optional.empty() if there is no row present.
Customers c;
if (customer.isPresent()) {
c = customer.get();
c.setEmail("newemail#gmail.com");
c.setPhone("9420420420");
customerRepo.save(c);
} else {
c = new Customer(0, "name", "email", "5451515478");
customerRepo.save(c);
}
Pass the ID as 0 and JPA will insert a new row with the ID generated according to the sequence generator.
Although I never recommend using a number as an ID, if possible use a randomly generated UUID for the primary key, it will qurantee uniqueness and avoid any unexpected behaviour that may come with sequence generators.
With spring JPA it's pretty simple to implement this with clean java code.
Using Spring Data JPA's method T getOne(ID id), you're not querying the DB itself but you are using a reference to the DB object (proxy). Therefore when updating/saving the entity you are performing a one time operation.
To be able to modify the object Spring provides the #Transactional annotation which is a method level annotation that declares that the method starts a transaction and closes it only when the method itself ends its runtime.
You'd have to:
Start a jpa transaction
get the Db reference through getOne
modify the DB reference
save it on the database
close the transaction
Not having much visibility of your actual code I'm gonna abstract it as much as possible:
#Transactional
public void saveOrUpdate(DeviceMetric metric) {
DeviceMetric deviceMetric = metricRepository.getOne(metric.getId());
//modify it
deviceMetric.setName("Hello World!");
metricRepository.save(metric);
}
The tricky part is to not think the getOne as a SELECT from the DB. The database never gets called until the 'save' method.
Considering an IT with Spring-Boot and JUnit that would test whether a collection returned from database contains all needed elements. What would be the best way to do that?
To illustrate, consider a JPA class/entity such as the following:
class Person {
Integer id;
String name;
String lastName;
Address address;
Account account;
}
Consider that ids of Person, Address and Account would be auto-generated, so I can't infer them.
Any help will be appreciated.
I identity 3 points :
1) Invoke the method under test that is save and flush the entity instance with the JpaRepository dedicated to your entity
2) Make sure that your integration test is reliable/valuable.
Here it matters to clear the first level cache of JPA (EntityManager.clear()) to test the actual retrieval from the database. The cache may hide some issue in your mapping that will be seen only as the object is actually found from the database.
3) Assert the expected behavior that is retrieve the saved entity from the DB and assert its state according to your expected.
For asserting fields of a object AssertJ could interest you.
It doesn't force you to override equals()/hashCode() and it is very simple and meaningful.
As you want to assert nested objects I advise to use a distinct assertThat() by object.
For example :
Person person = new Person()...;
// action
personRepository.saveAndFlush(person);
// clear the first level cache
em.clear();
// assertions
Optional<Person> optPerson = personRepository.findById(person.getId());
// JUnit
Assert.assertTrue(optPerson.isPresent());
// AssertJ
person = optPerson.get();
Assertions.assertThat(person)
.extracting(Person::getName, Person::getLastName)
.containsExactly("expected name", "expected last name");
Assertions.assertThat(person.getAccount())
.extracting(Account::getFoo, Account::getBar)
.containsExactly("expected foo", "expected bar");
Assertions.assertThat(person.getAddress())
.extracting(Address::getStreet, Address::getZip)
.containsExactly("expected street", "expected zip");
I'm trying to construct a stream pipeline which is not really straightforward and it gets me puzzled.
The idea is that I have a class containing a set of entities I want to traverse. Application class defines a field Set<Document> documents. Inside of these docs I have a field DocumentFile documentFile. I filter the stream based on the name of this document file, but the result that I need is the id of Document entity.
So the method goes like this:
private long retrieveSmth(String docName, long applicationId) {
final Application application = this.applicationDao.get(applicationId);
final long docId = application.getDocuments()
.stream()
.map(Document::getDocumentFile)
.filter(doc -> doc.getDocumentFileName().equals(docName))
...
}
At this point I get stuck questioning myself how do I get the control back to Document level and retrieve the id of the document whose document file satisfies the condition. Is there a way to do this using Stream API?
If you map() to DocumentFile you cannot "go back" to the owner object of DocumentFile : the Stream<Document> was transformed into a Stream<DocumentFile>.
You should so not map and specify the object to test from Stream<Document>.filter() :
final long docId = application.getDocuments()
.stream()
.filter(doc -> doc.getDocumentFile().getDocumentFileName().equals(docName))
.map(Document::getId); // now it is possible
Note that you should avoid talking to strangers and this makes this bad smell :
doc.getDocumentFile().getDocumentFileName()
So it would be interesting to introduce a method matchesName() in Document that does the delegation and the equality test :
public boolean matchesName(String name){
return name.equals(getDocumentFile().getDocumentFileName());
}
In this way it sounds better :
.filter(doc -> doc.matchesName(docName))