Spring Jpa/Hibernate Transaction Force Insert Instead of Update

Spring JPA / Hibernate transaction force insert instead of update

I never did that before, but a little hack, would maybe do the job.

There is a Persistable interface for the entities. It has a method boolean isNew() that when implemented will be used to "assess" if the Entity is new or not in the database. Base on that decision, EntityManager should decide to call .merge() or .persist() on that entity, after You call .save() from Repository.

Going that way, if You implement isNew() to always return true, the .persist() should be called no mater what, and error should be thrown after.

Correct me If I'm wrong. Unfortunately I can't test it on a live code right now.

Documentation about Persistable: http://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/domain/Persistable.html

JPA firing INSERT before SELECT during an UPDATE?

This is the normal flushing behaviour of JPA. When you change an entity managed by JPA these changes get flushed to the database before a query gets executed.

You did change a User entity by adding an Address to it.

To fix that add the Address only when it is fully constructed, i.e. it's AddressType is set.

Note: I'm sure this is a duplicate, but I can't find it. But there is this closely related question: JPA auto flush before any query

why saveAll() always inserts data instead of update it?

Looks like I found the root of this behaviour.

Main App launcher look like:

@AllArgsConstructor
@SpringBootApplication
public class Application implements CommandLineRunner {

private final DataService dataService;
private final QrReaderServer qrReaderServer;
private final MonitoringService monitoringService;

@Override
public void run(String... args) {
dataService.fetchAndStoreData();
monitoringService.launchMonitoring();
qrReaderServer.launchServer();
}

All 3 steps have strict execution sequence. And the first one has to repeat for updating data locally if it is needed. Two other just servers which work with stored data only.

Where the first method look like:

@Scheduled(fixedDelay = 15_000)
public void fetchAndStoreData() {
log.debug("START_DATA_FETCH");

carParkService.fetchAndStoreData();
entityService.fetchAndStoreData();
assignmentService.fetchAndStoreData();
permissionService.fetchAndStoreData();
capacityService.fetchAndStoreData();

log.debug("END_DATA_FETCH");
}

Also, this execution is scheduled as well.

When the app starts it tried to execute this fetching twice:

2020-12-14 14:00:46.208 DEBUG 16656 --- [pool-3-thread-1] c.s.s.s.data.impl.DataServiceImpl        : START_DATA_FETCH
2020-12-14 14:00:46.208 DEBUG 16656 --- [ restartedMain] c.s.s.s.data.impl.DataServiceImpl : START_DATA_FETCH

2 threads run at the same catch and store in parallel - trying to insert data. (tables are recreated at every start).

All later fetches are fine, they are executed only by @Sceduled thread.

If comment @Sceduled - it will work fine without any Exceptions.


SOLUTION:

Added additional boolean property to service class:

@Getter
private static final AtomicBoolean ifDataNotFetched = new AtomicBoolean(true);

@Override
@Scheduled(fixedDelay = 15_000)
@Order(value = Ordered.HIGHEST_PRECEDENCE)
public void fetchAndStoreData() {
ifDataNotFetched.set(true);
log.debug("START_DATA_FETCH");

// fetch and store data with `saveAll()`

log.debug("END_DATA_FETCH");
ifDataNotFetched.set(false);
}

And control the value after the application is started:

@Value("${sharepark.remote-data-fetch-timeout}")
private int dataFetchTimeout;
private static int fetchCounter;

@Override
public void run(String... args) {
waitRemoteDataStoring();
monitoringService.launchMonitoring();
qrReaderServer.launchServer();
}

private void waitRemoteDataStoring() {
do {
try {
if (fetchCounter == dataFetchTimeout) {
log.warn("Data fetch timeout reached: {}", dataFetchTimeout);
}

Thread.sleep(1_000);

++fetchCounter;
log.debug("{} Wait for data fetch one more second...", fetchCounter);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
} while (DataServiceImpl.getIfDataNotFetched().get() && fetchCounter <= dataFetchTimeout);
}

force insert with spring data jpa

Forcing insert for on entity whose id is not null is not allowed in Spring Data JPA.

Normally an Id generator is used for a primary key that ensures ids generated are unique. (e.g. @GeneratedValue(strategy = GenerationType.AUTO))

This behavior in my opinion is correct. In an ORM, use of new operator indicates intention to create a new entity with an implicit requirement to assign it a unique id.

But if you wish to try it anyway you can look into a custom @GenericGenerator.

http://www.georgestragand.com/jpaseq.html

Spring crud repository - save() tries to insert new rows for many-to-one relationship in child property

this problem is happen when you don't declare CasCadeType for Entity associated, Cascade contain several Type :

  1. CascadeType.PERSIST : cascade type presist means that save() or persist() operations cascade to related entities.
  2. CascadeType.MERGE : cascade type merge means that related entities are merged when the owning entity is merged.
  3. CascadeType.REFRESH : cascade type refresh does the same thing for the refresh() operation.
  4. CascadeType.REMOVE : cascade type remove removes all related entities association with this setting when the owning entity is deleted.
  5. CascadeType.DETACH : cascade type detach detaches all related entities if a “manual detach” occurs.
  6. CascadeType.ALL : cascade type all is shorthand for all of the above cascade operations.

the default value is Type.ALL for your case you must change cascadeType base on requirement.



Related Topics



Leave a reply



Submit