Using Hibernate #SQLDelete for Soft Delete across All Entities - mysql

We have a fairly complex data model and are using Hibernate and Spring Data JPA on top of MySQL. We have a base class that all domain objects extend to minimize boiler plate code. I would like to be able to add soft delete functionality across all of our domain objects using only this class. However, #SQLDelete requires the table name in the clause:
#SQLDelete(sql="UPDATE (table_name) SET deleted = '1' WHERE id = ?")
#Where(clause="deleted <> '1'")
Does anybody know of a way to generalize the SQLDelete statement and allow the extending domain objects to populate their own table names?

If you use hibernate and #SQLDelete, there's no easy solution to your question. But you can consider another approach to soft delete with Spring Data's expression language:
#Override
#Query("select e from #{#entityName} e where e.deleteFlag=false")
public List<T> findAll();
//recycle bin
#Query("select e from #{#entityName} e where e.deleteFlag=true")
public List<T> recycleBin();
#Query("update #{#entityName} e set e.deleteFlag=true where e.id=?1")
#Modifying
public void softDelete(String id);
//#{#entityName} will be substituted by concrete entity name automatically.
Rewrite base repository like this. All sub repository interfaces will have soft delete ability.

Another approach, which could be more flexible.
On Entity level create
#MappedSuperclass
public class SoftDeletableEntity {
public static final String SOFT_DELETED_CLAUSE = "IS_DELETED IS FALSE";
#Column(name = "is_deleted")
private boolean isDeleted;
...
}
Update your Entity which should be soft deletable
#Entity
#Where(clause = SoftDeletableEntity.SOFT_DELETED_CLAUSE)
#Table(name = "table_name")
public class YourEntity extends SoftDeletableEntity {...}
Create a custom Interface Repository which extends the Spring's Repository. Add default methods for soft delete. It should be as a base repo for your Repositories. e.g.
#NoRepositoryBean
public interface YourBaseRepository<T, ID> extends JpaRepository<T, ID> {
default void softDelete(T entity) {
Assert.notNull(entity, "The entity must not be null!");
Assert.isInstanceOf(SoftDeletableEntity.class, entity, "The entity must be soft deletable!");
((SoftDeletableEntity)entity).setIsDeleted(true);
save(entity);
}
default void softDeleteById(ID id) {
Assert.notNull(id, "The given id must not be null!");
this.softDelete(findById(id).orElseThrow(() -> new EmptyResultDataAccessException(
String.format("No %s entity with id %s exists!", "", id), 1)));
}
}
NOTE: If your application doesn't have the hard delete then you could add
String HARD_DELETE_NOT_SUPPORTED = "Hard delete is not supported.";
#Override
default void deleteById(ID id) {
throw new UnsupportedOperationException(HARD_DELETE_NOT_SUPPORTED);
}
#Override
default void delete(T entity) {
throw new UnsupportedOperationException(HARD_DELETE_NOT_SUPPORTED);
}
#Override
default void deleteAll(Iterable<? extends T> entities) {
throw new UnsupportedOperationException(HARD_DELETE_NOT_SUPPORTED);
}
#Override
default void deleteAll() {
throw new UnsupportedOperationException(HARD_DELETE_NOT_SUPPORTED);
}
Hope it could be useful.

Related

Junit test case not running for mock JPA

I have written sample CRUD methods.I have written JUnit test cases for Service component but getting "address id not found.." when I run the test.
#Test
public void updateAddressTest() throws ResourceNotFoundException {
Optional<Person> p = Optional.ofNullable(new Person( "Pranya", "Pune"));
when(personRepository.existsById(1L)).thenReturn(true);
Optional<Address> address = Optional.ofNullable(new Address( "zzz", "hyd","tel","1234"));
when(repository.findById(1L)).thenReturn(address);
Address addr1 = new Address( "zzz", "hyd","tel","1234");
when(repository.save(addr1)).thenReturn(addr1);
Address add= service.updateAddress(new Long(1L), new Long(1L),addr1);
assertEquals(addr1,add );
}
#Service
public class AddressService {
#Autowired
private AddressRepository repository;
#Autowired
private PersonRepository personRepository;
public Address updateAddress(Long personId,
Long addressId,Address addrRequest) throws ResourceNotFoundException {
if (!personRepository.existsById(personId)) {
throw new ResourceNotFoundException("personId not found");
}
return repository.findById(addressId).map(address -> {
address.setCity(addrRequest.getCity());
address.setState(addrRequest.getState());
address.setStreet(addrRequest.getStreet());
address.setPostalCode(addrRequest.getPostalCode());
Person p = new Person();
p.setId(personId);
address.setPerson(p);
return repository.save(address);
}).orElseThrow(() -> new ResourceNotFoundException("address id not found.."));
}
}
Most likely repository.save(address) returns null. You are mocking the method, but only for the argument equal addr1. Inside the AddressService a different instanceof the address is created. I'm guessing that Address class does not implement equals method (or includes the person field in the implementation), so when(repository.save(addr1)).thenReturn(addr1) does not match the call and null is returned.
To solve the problem try using Mockito.doAnswer instead of the Mockito.when:
Mockito.doAnswer(invocation -> invocation.getArguments()[0]).when(repo).save(Mockito.any(Address.class));

How handle currents updates in spring-boot hibernate problem? Also need to make app scalable

Project type :- Spring-boot JPA project
Hi,
I have below Rest service which increments a number in database.
#RestController
public class IncrementController {
#Autowired
MyNumberRepository mynumberRepository;
#GetMapping(path="/incrementnumber")
public String incrementNumber(){
Optional<MyNumber> mynumber = mynumberRepository.findById(1);
int i = mynumber.get().getNumber();
System.out.println("value of no is "+i);
i = i+1;
System.out.println("value of no post increment is "+i);
mynumber.get().setNumber(i);
MyNumber entity = new MyNumber();
entity.setId(1);
entity.setNumber(i);
mynumberRepository.save(entity);
return "done";
}
}
Entity is as below :-
#Entity
#Table(name = "my_number")
public class MyNumber {
#Id
private Integer id;
private Integer number;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getNumber() {
return number;
}
public void setNumber(Integer number) {
this.number = number;
}
}
Below is the Repository :-
public interface MyNumberRepository extends JpaRepository<MyNumber, Integer>{
}
The service works well when I call increment number sequentially , but when concurrent threads call the incrementservice then i get non consistent results. How can I handle this situation ?
Also have to deploy the app on multiple places and connecting to same DB. i.e Scalability concern.
Thanks,
Rahul
You must use a pessimistic lock. This will issue a SELECT FOR UPDATE and lock the row for the transaction and it's not possible for another transaction to overwrite the row.
public interface MyNumberRepository extends JpaRepository<MyNumber, Integer> {
#Lock(LockModeType.PESSIMISTIC_WRITE)
Optional<MyNumber> findById(Integer id);
}
And then you have to make your REST method transactional by adding #Transactional
#RestController
public class IncrementController {
#Autowired
MyNumberRepository mynumberRepository;
#Transactional
#GetMapping(path="/incrementnumber")
public String incrementNumber(){
Optional<MyNumber> mynumber = mynumberRepository.findById(1);
int i = mynumber.get().getNumber();
System.out.println("value of no is "+i);
i = i+1;
System.out.println("value of no post increment is "+i);
mynumber.get().setNumber(i);
MyNumber entity = new MyNumber();
entity.setId(1);
entity.setNumber(i);
mynumberRepository.save(entity);
return "done";
}
}
Above solution will work , but i feel you are doing over-engineering for very simple problem.
My recommendation would be to use database sequence.I feel your requirement is quite straight forward.In your service u can simply call getnextvalue on the sequence and then set the value in the Id field.This way u don't have to manage locks also as Database will do that for you.
In oracle particularly sequences are managed in a different transactions . So if ur calling code fails with exception , still the value of sequence will be incremented . This will ensure that multi-threads will not see the same value of the sequence in case of exceptions.
Instead of locking transaction, you could also use an Oracle sequence or MySQL "AUTO_INCREMENT" feature which will prevent any ID being returned twice.
https://community.oracle.com/thread/4156674
Thread safety of MySql's Select Last_Insert_ID

Spring JPA select one column doesn't work

I need to retrieve all the records for players column in sports table. SportsRepository method like below.
#SuppressWarnings("unused")
#Repository
public interface SportsRepository extends JpaRepository<Sport, Long> {
public static final String GET_Players="SELECT players FROM Sport";
#Query(value = GET_Players, nativeQuery = true)
public static List<String> getPlayers();
}
After that I type "gradlew clean test" and see. Then it will generate an error saying "error: missing method body, or declare abstract". Why is that? Do we need to implement the body. Because JPA query should give the output as I could understand. Please anyone knows?
In a JAVA interface, when you try to define a static method, you should give it a method body, but here I think the only thing you should do is remove static from your method:
#SuppressWarnings("unused")
#Repository
public interface SportsRepository extends JpaRepository<Sport, Long> {
public static final String GET_Players="SELECT players FROM Sport";
#Query(value = GET_Players, nativeQuery = true)
public List<String> getPlayers();
}
Also access modifier public is not necessary here as well, because java take public as default when define a method in interface.
Note: I noticed that you assigned nativeQuery with true, you should confirm the GET_Players is sql, not a jql.

How to retrieve a List of unique String fields from a column in a table with Spring Data JPA?

I have a table with multiple lines and columns. I want to retrieve with JpaRepository a List of all the fields of a column.
Should I write a query as:
SELECT DISTINCT field2 FROM entity;
And annotate the method with #Query(QUERY_STRING)
or is there an easier way using JPA ?
Thank you.
If you need to get a value list of one field you can do the following:
public interface MyEntityRepo extends JpaRepository<MyEntity, Integer> {
#Query("select distinct e.field1 from MyEntity e")
List<String> getField1Values();
}
If you need to get a value list for many of fields I can propose you this trick with dynamic projection:
public interface MyEntityRepo extends JpaRepository<MyEntity, Integer> {
// T - it's a type of a concrete projection
<T extends OneField> List<T> getDistinctBy(Class<T> type);
default <T extends OneField> List<?> getFieldValues(Class<T> type) {
return getDistinctBy(type)
.stream()
.map(T::getValue)
.collect(Collectors.toList());
}
}
// T - it's a type of the table field
public interface OneField<T> {
default T getValue() {
return null;
}
}
Then create projections for your table fields which value list you need to get and invoke method getFieldValues for each projection, for example:
public interface Field1 extends OneField<String> {
#Override
default String getValue() {
return getField1();
}
String getField1();
}
List<?> values = repo.getFieldValues(Field1.class);
Using the JPA criteria api, something akin to:
CriteriaQuery<Entity3450862> criteria = builder.createQuery(Entity3450862.class)
Root root = criteria.from(Entity3450862.class);
Set<Entity3450862> yourDesiredResult = new HashSet<>();
yourDesiredResult.addAll(criteria.select(root.get("T")).list());
should sort you. This code is untested, but should point you in the right direction.

How to prevent EF4.1 from creating a DB if it doesn't exist?

I'm using EF4.1 with MVC3 and I need an override to prevent EF from creating a db if it doesn't exist. Instead of creating a new db I would like to catch the error and report that the initial catalog (the database name) is invalid in the connect string.
However, during development I would like to allow for updates for new classes/properties to create according tables/cols in the database.
Is there a best practice or pattern here?
In my application i am completly disable context initializer and handle database mapping and schema manually.
For example :
public class AppDbContext : DbContext
{
public IDbSet<Account> Accounts { get; set; }
public AppDbContext() : base("connection_string")
{
Database.SetInitializer<AppDbContext>(null); // Important! Dont use entity framework initializer !important
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
/* Register custom mapping class */
modelBuilder.Configurations.Add(new AccountMapper());
base.OnModelCreating(modelBuilder);
}
}
And custom mapping :
public class AccountMapper : EntityTypeConfiguration<Account>
{
/// <summary>
/// Employee entity mapper
/// </summary>
public AccountMapper()
{
ToTable("accounts");
HasKey(x => x.Id);
...
}
}
I would suggest looking into the EF database initializer, specifically the IDatabaseInitializer interface.
If you just want it to stop creating the database when it doesn't exist, then just set the Initializer to null. But if you want to log the event or something along those lines then simply create your own IDatabaseInitializer - it's not hard.
You can then set the initializer Application_Start in your global.asax.cs like so:
Database.SetInitializer(new YourCustomInitializer());
As a bonus, here's an example IDatabaseInitializer that I use to run database migrations (using FluentMigrator)... it's extremely handy if I do say so myself!
public class MigrationsDbContextInitializer : IDatabaseInitializer<YourDbContext>
{
private static readonly ILog Logger = LogManager.GetLogger(typeof(MigrationsDbContextInitializer));
public void InitializeDatabase(YourDbContext context)
{
var announcer = new BaseAnnouncer(x => Logger.Info(x));
var runnerContext = new RunnerContext(announcer)
{
Database = "sqlserver2008",
Connection = context.Database.Connection.ConnectionString,
Target = "YourEntitiesNamespace",
PreviewOnly = false,
Task = "migrate"
};
new TaskExecutor(runnerContext).Execute();
}
}