Spring data JPA expression using deleteBy - spring-boot

I am using spring-data-jpa to perform delete operation.
Currently, I have a query like:
#Modifying
#Transactional
#Query("Delete from student s where(:studentName is null Or s.studentName =:studentName) and s.studentId IN(:studentIds)")
int deleteByStudentIdAndRollNo(#Param("studentName") String studentName, #Param("studentIds") List<Integer> studentIds)
The above query is working fine and is returning a count of successfully deleted rows from the table.
But the problem is, now I have to return deleted List<Students> instead of the number of deleted records.
I am aware of the fact that #Query will always return the count of successfully deleted records.
I tried to use deleteBy like below:
List<Student> deleteByStudentIdStudentNameIsNullOrStudentNameIsAndStudentIdIN
, But that's not working.
Is there any way to get list of deleted students?

This article show how to return deleted records: Spring Data JPA – Derived Delete Methods
And try maybe this code:
List<Student> deleteAllByStudentNameIsNullOrStudentNameAndStudentIdIn(String studentName, List<Integer> studentIds);
But can't be sure that will be work because I don't know your Student's model structure.

Related

Spring JPA repository method to get sorted distinct and non-null values

To get distinct data based on multiple columns and exclude NULL values on a column and sort the result in SQL, I would write query like:
SELECT DISTINCT CAR_NUMBER, CAR_NAME
FROM CAR
WHERE CAR_NUMBER IS NOT NULL
ORDER BY CAR_NUMBER
This would return me rows with distinct values for CAR_NUMBER and CAR_NAME and it would exclude any rows having CAR_NUMBER = NULL and finally, it would sort the result by CAR_NUMBER.
However, In Spring JPA, I gather you can use either methods named based on your entity fields or using #Query annotation.
I am trying to do this:
List<Car> findDistinctByCarNumberAndCarNameAndCarNumberIsNotNull(Sort sort);
, and to call this method like:
myRepo.findDistinctByCarNumberAndCarNameAndCarNumberIsNotNull(Sort.by("carNumber"));
but this is failing on Maven > Install with error like "findDistinctByCarNumberAndCarNameAndCarNumberIsNotNull(Sort sort) expects at least 1 arguments but only found 0".
Similarly, I tried using #Query like below but with same effect:
#Query(SELECT DISTINCT c.carNumber, c.carName FROM carEntity c WHERE c.carNumber IS NOT NULL ORDER BY c.carNumber)
List<Car> findAllCars();
I figured out the problem. Following is how I solved it:
In my repository:
#Query("select distinct c.carNumber, c.carName from CarEntity c where c.carNumber is not null")
List<Object> findAllDistinctRegions(Sort sort);
Important here to realize is that #Query returns List<Object>, not List<Car>.
Next, in my service, call this method:
List<Object> carData = carRepository.findAllDistinctCars(Sort.by("carNumber"));
That worked finally fine; however, I run into another problem where I had to do necessary conversion from List to List.
// This is bit tricky as the returned List<Object> is actually
// List<Object[]>. Basically, each field returned by the #Query
// is placed into an array element.
//To solve it, I had to do following:
List<Car> cars = new ArrayList<Car>();
for(Object data: carsData) {
Object[] obj = (Object[]) data;
cars.add(new CarDto((Short) obj[0], ((String) obj[1]));
}
I just remembered there is a better way to solve this than that helper function that you described in your answer and thought I would share it.
Projection in JPQL would be a cleaner way to create your DTO:
#Query("SELECT DISTINCT new com.yourdomain.example.models.MyDto(c.carNumber, c.carName)
FROM CarEntity c WHERE c.carNumber is not null")
List<CarDto> findAllDistinctRegions(Sort sort);

Spring Data / Hibernate save entity with Postgres using Insert on Conflict Update Some fields

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.

Mapping many-to-many IN statement into JPA (Spring Boot)

I have created two entities in JPA, Listing and ItemType - these exist in a many-to-many relationship (Hibernate auto-generates a junction table). I'm trying to find the best way to create a query which accepts a dynamic list of item type Strings and returns the IDs of all listings which match the specified item types, but I am a recent initiate in JPA.
At present I'm using JpaRepository to create relatively simple queries. I've been trying to do this using CriteriaQuery but some close-but-not-quite answers I've read elsewhere seem to suggest that because this is in Spring, this may not be the best approach and that I should be handling this using the JpaRepository implementation itself. Does that seem reasonable?
I have a query which doesn't feel a million miles away (based on Baeldung's example and my reading on WikiBooks) but for starters I'm getting a Raw Type warning on the Join, not to mention that I'm unsure if this will run and I'm sure there's a better way of going about this.
public List<ListingDTO> getListingsByItemType(List<String> itemTypes) {
List<ListingDTO> listings = new ArrayList<>();
CriteriaQuery<Listing> criteriaQuery = criteriaBuilder.createQuery(Listing.class);
Root<Listing> listing = criteriaQuery.from(Listing.class);
//Here Be Warnings. This should be Join<?,?> but what goes in the diamond?
Join itemtype = listing.join("itemtype", JoinType.LEFT);
In<String> inClause = criteriaBuilder.in(itemtype.get("name"));
for (String itemType : itemTypes) {
inClause.value(itemType);
}
criteriaQuery.select(listing).where(inClause);
TypedQuery<Listing> query = entityManager.createQuery(criteriaQuery);
List<Listing> results = query.getResultList();
for (Listing result : results) {
listings.add(convertListingToDto(result));
}
return listings;
}
I'm trying to understand how best to pass in a dynamic list of names (the field in ItemType) and return a list of unique ids (the PK in Listing) where there is a row which matches in the junction table. Please let me know if I can provide any further information or assistance - I've gotten the sense that JPA and its handling of dynamic queries like this is part of its bread and butter!
The criteria API is useful when you need to dynamically create a query based on various... criteria.
All you need here is a static JPQL query:
select distinct listing from Listing listing
join listing.itemTypes itemType
where itemType.name in :itemTypes
Since you're using Spring-data-jpa, you just need to define a method and annotate it with #Query in your repository interface:
#Query("<the above query>")
List<Listing> findByItemTypes(List<String> itemTypes)

Spring data - Order by multiplication of columns

I came to a problem where I need to put ordering by multiplication of two columns of entity, for the sake of imagination entity is:
#Entity
public class Entity {
#Column(name="amount")
private BigDecimal amount;
#Column(name="unitPprice")
private BigDecimal unitPrice;
.
.
.
many more columns
}
My repo interface implements JpaRepository and QuerydslPredicateExecutor,
but I am struggling to find a way to order my data by "amount*unitPrice",
as I can't find a way to put it into
PageRequest (new Sort.Order(ASC, "amount * unitPrice"))
without having PropertyReferenceException: No property amount * unitPrice... thrown.
I can't user named query, as my query takes quite massive filter based on user inputs (can't put where clause into query, because if user hasn't selected any value, where clause can't just be in query).
To make it simple. I need something like findAll(Predicate, Pageable), but I need to force that query to order itself by "amount * unitPrice", but also have my Preditate (filter) and Pageable (offset, limit, other sortings) untouched.
Spring Sort can be used only for sorting by properties, not by expressions.
But you can create a unique sort in a Predicate, so you can add this sort-predicate to your other one before you call the findAll method.

SimpleJpaRepository Count Query

I've modified an existing RESTful/JDBC application i have to work with new features in Spring 4... specifically the JpaRepository. It will:
1) Retrieve a list of transactions for a specified date. This works fine
2) Retrieve a count of transactions by type for a specified date. This is not working as expected.
The queries are setup similarly, but the actual return types are very different.
I have POJOs for each query
My transactions JPA respository looks like:
public interface MyTransactionsRepository extends JpaRepository<MyTransactions, Long>
//My query works like a charm.
#Query( value = "SELECT * from ACTIVITI_TMP.BATCH_TABLE WHERE TO_CHAR(last_action, 'YYYY-MM-DD') = ?1", nativeQuery = true )
List< MyTransactions > findAllBy_ToChar_LastAction( String lastActionDateString );
This returns a list of MyTransactions objects as expected. Debugging, i see the returned object as ArrayList. Looking inside the elementData, I see that each object is, as expected, a MyTransactions object.
My second repository/query is where i'm having troubles.
public interface MyCountsRepository extends JpaRepository<MyCounts, Long>
#Query( value = "SELECT send_method, COUNT(*) AS counter FROM ACTIVITI_TMP.BATCH_TABLE WHERE TO_CHAR(last_action, 'YYYY-MM-DD') = ?1 GROUP BY send_method ORDER BY send_method", nativeQuery = true )
List<MyCounts> countBy_ToChar_LastAction( String lastActionDateString );
This DOES NOT return List as expected.
The object that holds the returned data was originally defined as List, but when I inspect this object in Eclipse, I see instead that it is holding an ArrayList. Drilling down to the elementData, each object is actually an Object[2]... NOT a MyCounts object.
I've modified the MyCountsRepository query as follows
ArrayList<Object[]> countBy_ToChar_LastAction( String lastActionDateString );
Then, inside my controller class, I create a MyCounts object for each element in List and then return List
This works, but... I don't understand why i have to go thru all this?
I can query a view as easily as a table.
Why doesn't JPA/Hibernate treat this as a simple 2 column table? send_method varchar(x) and count (int or long)
I know there are issues or nuances for how JPA treats queries with counts in them, but i've not seen anything like this referenced.
Many thanks for any help you can provide in clarifying this issue.
Anthony
That is the expected behaviour when you're doing a "group by". It will not map to a specific entity. Only way this might work is if you had a view in your database that summarized the data by send_method and you could map an entity to it.

Resources