Estou tendo um problema com JPA / Hibernate onde ele está executando um INSERT seguido de um UPDATE para um novo registro que ele persiste.
Quero eliminar a execução do UPDATE, pois não faz sentido nenhum ele executar este segundo comando, pois não estou explicitamente fazendo isso. Alguma ideia do que pode causar essa redundância?
O modelo de entidade é assim (sem entrar no mérito do design):
PriceSummary [1] --> [N] ServicePriceSummary [1] --> [N] ServicePriceBreakdown [1] --> [N] MonthBreakdown
Devido a uma lógica de negócio particular, pode chegar a 10.000 ou 20.000 (sim 20 mil) registros de MonthBreakdown que são criados nos cálculos para uma grande proposta.
Fazendo um profiling da nossa aplicação, este é um grande gargalo, onde as operações do MonthBreakdown de INSERT e UPDATE, juntas, representam mais de 90% do processamento.
Se conseguir eliminar o UPDATE em já reduz em quase 50% o tempo de processamento.
Meu código (resumido) é, basicamente, o seguinte:
@Service
@Transactional
public class PricingService {
/** Método chamado para realizar a transação. */
public PricingContext calculate(Integer id) {
Proposal proposal = proposalRepository.findOne(id);
PriceSummary summary = new PriceSummary(proposal);
PricingContext context = new PricingContext(summary);
List<Service> services = serviceRepository.findByProposalId(id);
services.forEach(service -> calculate(service, context));
priceSummaryRepository.saveAndFlush(summary); // JpaRepository interface
return context;
}
public void calculate(Service service, PricingContext context) {
List<Rates> rates = ratesService.findRatesForProposal(context.getProposal()); // do some JPA queries and return data
// faz uns calculos muito loucos aqui e atualiza PricingSummary em context
}
}
Entidade PriceSummary:
@Entity
@Table(name="price_summary")
public class PriceSummary {
@Id @Column(name = "id")
private Integer id;
@Column(name = "lock_version")
private Integer lockVersion;
@Column(name = "price")
private BigDecimal price;
@OneToMany(cascade = CascadeType.ALL, mappedBy = "priceSummary", orphanRemoval = true)
private final List<ServicePriceSummary> servicePriceSummaries = new ArrayList<>();
// getters & setters
}
Entidade ServicePriceSummary:
@Entity
@Table(name="srv_price_summary")
public class ServicePriceSummary {
@Id @Column(name = "id")
private Integer id;
@ManyToOne @JoinColumn(name = "price_summary_id")
private PriceSummary priceSummary;
@OneToMany(cascade = CascadeType.ALL, mappedBy = "servicePriceSummary", orphanRemoval = true)
private final List<ServicePriceBreakdown> servicePriceBreakdowns = new ArrayList<>();
// getters & setters
}
Entidade ServicePriceBreakdown:
@Entity
@Table(name="srv_price_breakdown")
public class ServicePriceBreakdown {
@Id @Column(name = "id")
private Integer id;
@ManyToOne @JoinColumn(name = "srv_price_summary_id")
private ServicePriceSummary servicePriceSummary;
@Column(name = "country_code")
private String countryCode;
@OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "servicePriceBreakdown", orphanRemoval = true)
private final List<MonthBreakdown> monthBreakdowns = new ArrayList<>();
// getters & setters
}
Entidade MonthBreakdown:
@Entity
@Table(name="month_breakdown")
public class MonthBreakdown {
@Id @Column(name = "id")
private Integer id;
@ManyToOne @JoinColumn(name = "srv_price_breakdown_id")
private ServicePriceBreakdown servicePriceBreakdown;
@Column(name = "month_number")
private Integer month;
@Column(name = "cost")
private BigDecimal cost;
// getters & setters
}
Curiosamente, se olhar o relatório do profiling do JProfiler, parece que ele executa o INSERT e depois o UPDATE quando se adiciona o registro em uma lista de um entidade. Porém no log SQL do Hibernate ele primeiro lista todos os INSERT e em seguida todos os UPDATE.
Outra estratégia foi configurar o Batch Size do Hibernate. Com isso tivemos algum ganho (20 a 40% em alguns casos), mas ainda não resolveu o problema.
Uma outra estratégia que pensamos foi remover a configuração de “cascade = CascadeType.ALL” da classe ServicePriceBreakdown no atributo monthBreakdowns e não deixar o Hibernate executar os INSERTS para o MonthBreakdown. Então, após salvar os objetos do grafo, reuno uma lista dos MonthBreakdown e explicitamente salvo, visando evita o UPDATE e melhorar execução em batch. Faz sentido?
Estamos usando OpenJDK 1.8, Spring Boot 1.3.1, Tomcat 7.