0

I have many tables that belong to the same Project by ID. When I reload a Project with an existing ID, I need to clear all entities from the database.

Controller:

@CrossOrigin
@RequestMapping(value = "projects", method = RequestMethod.POST)
public ResponseEntity<?> uploadProject(MultipartFile file) {
    JsonDataDto projectDto = converterService.convertToDto(file, JsonDataDto.class);
    
    if(projectRepository.exists(projectDto.getId())) {
        // Delete all project entities from DB
        projectService.delete(projectDto.getId());
    }

    // Save project to DB
    importService.import(projectDto);
}

Project Service (delete):

@Service
@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.REPEATABLE_READ)
public class GenericProjectService implements ProjectService {
    // Fields

    @Override
    public void delete(UUID projectId) {
        entity1Repository.deleteByProjectId(projectId)
        ...
        // Most entities are associated with a project by a foreign key.
        // Some entities are not linked by a foreign key and are removed manually (entity1Repository for example)
        projectRepository.delete(projectId);
    }
}

Import Service (save):

@Service
public class GenericImportService implements ImportService {
    // Fields

    @Override
    @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.REPEATABLE_READ)
    public void import(JsonDataDto projectDto)  {
        Collection<Entity1> entity1 = projectDto.getEntity1()
                                                .stream().map(e -> e1Repository.save(e1Mapper.to(e))).collect(...);

        Map<UUID, Type> types = new HashMap<>();
        Map<UUID, TypeDto> typeDtosById = projectDto.getTypes().stream()
            .collect(Collectors.toMap(TypeDto::getId, Function.identity()));

        for (UUID typeId : typeDtosById.keySet()) {
            saveType(typeId, typeDtosById, types, ...);
        }
    }

    private void saveType(...) {
        Type type = new Type();

        // Set fields and relations

        // Get exception here
        type = typeRepository.save(type);
        types.put(typeId, type);
    }
}

Type Class:

@Entity
@Data
@Table(name = "...", schema = "...")
public class Type {
    @Id
    private TypePK typeId;
    /*
        @Data
        @NoArgsConstructor
        @AllArgsConstructor
        @Embeddable
        public class TypePK implements Serializable {
            @Type(type = "pg-uuid")
            @Column(name = "id")
            private UUID id;

            @ManyToOne(cascade = CascadeType.ALL,fetch = FetchType.EAGER)
            @JoinColumn(name = "project_id", insertable = false, updatable = false)
            private Project project;
        }
    */

    // Fields

    @org.hibernate.annotations.Type(type = "pg-uuid")
    @Column(name = "parent_id")
    private UUID parentId;

    @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    @JoinColumns({
        @JoinColumn(name = "parent_id", referencedColumnName = "id", updatable = false, insertable = false), 
        @JoinColumn(name = "project_id", referencedColumnName = "project_id", updatable = false, insertable = false)})
    private Type parent;
}

When the project does not exist in database, the save is successful. If I delete project from controller, it will also be successfully deleted from the database.

If project exists in database and I try to save it again, I get an error: "Unable to find package.Type with id TypePK(id=7e8281fe-77b8-475d-8ecd-c70522f5a403, project=Project(id=8d109d33-e15e-ca81-5f75-09e00a81a194))"

The entities are removed from the database, but the save transaction is rolled back.

I tried to force close the transaction after delete but it did not help:

public void delete(UUID projectId) {
    TransactionStatus ts = TransactionAspectSupport.currentTransactionStatus();
    entity1Repository.deleteByProjectId(projectId)
    ...
    ts.flush();
}

The only way I found is, in fact, a crutch. I just wait a couple of seconds before starting save:

if(projectRepository.exists(projectDto.getId())) {
    // Delete all project entities from DB
    projectService.delete(projectDto.getId());
}

// Any timer
DateTime waitFor = DateTime.now().plusSeconds(2);
while(DateTime.now().isBefore(waitFor)) { }

// Save project to DB
importService.import(projectDto);
Shadow4571
  • 382
  • 2
  • 4
  • 10
  • Thread.sleep would be more appropriate as it does not hog 100% of core time like while loop – Antoniossss Jun 14 '22 at 10:25
  • @Antoniossss, I tried using "Thread.sleep(2000)" but I get the same error as without it. Is it possible that the transaction is processed in the current thread? In that case, it makes sense. We pause the thread and pause the processing of the transaction. When the thread continues executing, the transaction is not yet closed. – Shadow4571 Jun 14 '22 at 10:52
  • I didnt say it will fix anything. – Antoniossss Jun 14 '22 at 12:09

1 Answers1

0

I managed to solve the problem using the suggestion in this comment: https://stackoverflow.com/a/14369708/10871976

I added an adapter to the delete transaction. On successful deletion in the "afterCompletion" method, I call project saving.

Shadow4571
  • 382
  • 2
  • 4
  • 10