I would like to do the following:
Inserting the CityHistory into the database using JPA. The first time there is no data, so a new city will be inserted. (IT WORKS FINE) the (IDENTIFICATION) within the city table is a unique field.
What I want to achieve is when I am inserting the same city again is to reuse the existing field instead of trying to create a new one (identification will be like a city's unique name).
So how can I do that using JPA or Hibernate?
@Entity
public class CityHistory extends History implements Serializable {
@Id
@Column(name = "KEY_CITY_HISTORY", nullable = false, precision = 19)
private Long id;
@ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
@JoinColumn(name = "CITY_ID", nullable = false, foreignKey = @ForeignKey(name = "FK_CITY_ID"))
private City cityId;
@Column(name = "CITY_NAME", nullable = false)
private String cityName;
}
@Entity
public class City implements Serializable {
@Id
@Column(name = "KEY_CITY", nullable = false, precision = 19)
private Long id;
@Column(name = "IDENTIFICATION", nullable = false, unique = true)
private String identification;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "MUNICIPALITY_ID", foreignKey = @ForeignKey(name = "FK_MUNICIPALITY_ID"))
private Municipality municipalityId;
}
UPDATE Here is how I am writing the data to the database, It's a Spring Batch itemWriter
@Component
public class InfoItemWriter implements ItemWriter<Object> {
@Autowired
private CityHistoryRepository cityHistoryRepository;
@Override
public void write(List<? extends Object> items) throws Exception {
if (items.size() > 0 && items.get(0) instanceof CityHistory) {
cityHistoryRepository.saveAll((List<? extends CityHistory>) items);
}
}
}
CodePudding user response:
First of all thanks to all who tried to help!
Reading the resources that @Benjamin Maurer
provided:
I don't think you want the cascade on the ManyToOne side, see One-To-Many
The most common Parent – Child association consists of a one-to-many and a many-to-one relationship, where the cascade being useful for the one-to-many side only
As the relation I have is ManyToOne it was really not useful to use the cascade and doesn't serve my need.
I used a different approache to reach the goal. I have created a service where it validates the existence of a city, then adds a new city if it does not exist.
@Service
public class CityHistoryServiceImpl implements CityHistoryService {
@Autowired
CityRepository cityRepository;
@Autowired
CityHistoryRepository cityHistoryRepository;
@Override
public Optional<CityHistory> addCityHistory(City city, String cityName, ..) {
if (city != null && cityName != null) {
City city1 = addCityIfNotExist(city);
CityHistory cityHistory = new CityHistory();
cityHistory.setCityId(city1);
cityHistory.setCityName(cityName);
cityHistoryRepository.save(cityHistory);
return Optional.of(cityHistory);
}
return Optional.empty();
} ....
private City addCityIfNotExist(City city) {
City city1 = cityRepository.findFirstByBagId(city.getBagId());
if (city1 == null) {
city1 = cityRepository.save(city);
}
return city1;
}
}
CodePudding user response:
Hibernate will use the @Id
property of City to determine if it is new or not. When it is null
, Hibernate couldn't possibly know that a similar entry already exists.
So you need to perform a query to find each city first:
for (var history : histories) {
var cities = em.createQuery("select city from City city where city.identification = ?1", City.class)
.setParameter(1, history.getCityId().getIdentification())
.getResultList();
if (!cities.isEmpty()) {
history.setCityId(cities.get(0));
}
em.persist(history);
}
If you use Hibernate and City.identification
is unique and always non-null, you can use it as a NaturalID:
In City:
@NaturalId
private String identification;
Then:
for (var history : histories) {
var city = em.unwrap(Session.class)
.byNaturalId(City.class)
.using("identification", history.getCityId().getIdentification())
.getReference();
if (city != null) {
history.setCityId(city);
}
em.persist(history);
}
But if you do have City.id
set, i.e., not null, you can use EntityManager.merge
to get a managed entity:
for (var history : histories) {
City city = history.getCityId();
if (city.getId() != null) {
city = em.merge(city);
history.setCityId(city);
}
em.persist(history);
}
One more remark: We are not in the relational domain, but we are mapping object graphs. So calling your fields cityId and municipalityId is arguably wrong - even the type says so: City cityId
.
They are not just plain identifiers, but full fledged objects: City city
.