I'm trying to implement a config-database using MariaDB and Hibernate in Java. Let A be a Person who can have B Licenses. Each license b consists of a list of Cs (some object which holds a string and an int).
class A {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private long id;
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "b_id")
List<B> bs;
}
class B {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
#Column(name = "b_id")
private long id;
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "c_id")
List<C> bs;
}
class C {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
#Column(name = "c_id")
private long id;
String blabla;
int i;
}
I can get it to save, however I can not get it to update without leaving redundant fields when resaving the changed A (load A from database -> add B to A's Bs / add C to B's Cs -> save -> redundant fields stay in database).
What is the proper way to do this?
So far I tried using #ElementCollection, CascadeType.persist and orphanRemoval = true with no luck.
Add updatable false in #JoinColumn So it will not change child tables while saving parent
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "b_id",updatable=false)
List<B> bs;
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "c_id",updatable=false)
List<C> bs;
Related
I have two entities BookingLegEntity and BookingEntity which reference each other. But anytime I try to retrieve them from the database (e.g. via findByUuid), BookingLegEntity.belongsTo remains null.
Here are my entities:
#Entity
#Table(name = "BOOKING_LEG")
#SQLDelete(sql = "UPDATE BOOKING_LEG SET deleted = true WHERE id=?")
#Where(clause = "deleted=false")
public class BookingLegEntity {
#Id
#Column(name = "ID", unique = true, updatable = false)
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column(name = "UUID", nullable = false)
private UUID uuid;
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "BELONGS_TO")
private BookingEntity belongsTo;
// ..
#ManyToOne
#JoinColumn(name = "DISTRIBUTOR")
private DistributorEntity distributor;
#Column(name = "TRANSPORT_TYPE")
#Convert(converter = TripTypeEnumConverter.class)
private TripTypeEnum transportType;
// ...
}
#Entity
#Table(name="BOOKINGS")
#SQLDelete(sql = "UPDATE BOOKINGS SET deleted = true WHERE id=?")
#Where(clause = "deleted=false")
public class BookingEntity {
#Id
#Column(name="ID", unique=true, updatable = false)
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column(name="BOOKING_ID")
#Convert(converter = BookingIdConverter.class)
private BookingId bookingId;
#ManyToOne
#JoinColumn(name ="BOOKED_BY")
private UserEntity bookedBy;
// ..
#OneToMany(mappedBy = "belongsTo", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private Set<BookingLegEntity> bookingLegs = new HashSet<>();
// ...
}
Here is my repository:
#Repository
public interface BookingLegRepository extends JpaRepository<BookingLegEntity, Long> {
Optional<BookingLegEntity> findByUuid(UUID id);
// ...
}
The values in the database itself look correct:
What is really strange is that this has worked before (belongsTo was not null) but suddenly stopped working. Does anyone has any idea as to what we might do wrong here?
Do not use cascade = CASCADEType.ALL on your ManyToOne annotation, because removing one BookingLeg will cause a removal of all in corresponding Booking
The solution should be to use
cascade = CascadeType.DETACH,CascadeType.MERGE,CascadeType.PERSIST,CascadeType.REFRESH}) in its stead.
I would Truncate Cascade or Delete from Bookings where original_itinerary is null before i move on to using the new entities.
Sincerely hope it helps. (No hate if it doesn't pls)
Edit : i didnt see that comment by #dey, its my own. :P saw his comment after posting my ans
To start off, I will say that I have seen a lot of questions with the exact same exception message, however I did not find one that corresponded to the problem I'm having personally.
I have two classes that are related to one another as follows:
Parent class
public class MapArea {
#Id
#Column(nullable = false, unique = true)
private String name;
#ManyToOne(cascade=CascadeType.PERSIST)
#OnDelete(action = OnDeleteAction.NO_ACTION)
private MapArea parent;
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "area")
#JsonIgnore
private Set<AlternativeAreaName> alternativeNames;
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "data")
private Set<MapAreaData> mapAreaData;
#OneToOne(targetEntity = Expenses.class,
cascade = CascadeType.ALL)
#JoinColumn(name = "name", referencedColumnName = "area")
private Expenses expenses;
#OneToOne(targetEntity = Consumption.class,
cascade = CascadeType.ALL)
#JoinColumn(name = "name", referencedColumnName = "area")
#JsonIgnore
private Consumption consumption;
#Transient
private Set<RelatedMapArea> siblings;
#Transient
private Set<RelatedMapArea> children;
...
}
Child class
public class MapAreaData {
#Id
#JsonIgnore
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "num_bedrooms", nullable = false)
private Integer numBedrooms;
#Column(name = "property_type", nullable = false)
#Enumerated(EnumType.STRING)
private PropertyType propertyType;
#OneToOne(targetEntity = Property.class,
cascade = CascadeType.ALL)
#JoinColumn(name = "property_id")
private Property property;
#OneToOne(targetEntity = Rent.class,
cascade = CascadeType.ALL)
#JoinColumn(name = "rent_id")
private Rent rent;
#OneToOne(targetEntity = CalculatedStats.class,
cascade = CascadeType.ALL)
#JoinColumn(name = "calculated_stats_id")
private CalculatedStats calculatedStats;
...
}
Now the full error message that I get is this:
Caused by: org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: com.topfind.search_api_common.map_area_data.MapAreaData
In similar questions, I have found that most people did not use a CascadeType which was their issue. However, in this case I am using CascadeType.ALL everywhere and I'd expect it to be able to save new children MapAreaData whenever I attempt to save MapArea.
I'd appreciate any ideas towards fixing the issue.
Edit 1
I'm initializing the data in one with the following code (keep in my any method starting with createMock* creates a new object with ALL variables (including nested objects that are related) populated, however it does not persist the created object.
int top = 2;
DataQuality dataQuality = DataQuality.ROUGH;
MapArea ROUGH_mapArea = createMockArea("W5", null);
MapAreaData ROUGH_mapAreaData = ROUGH_mapArea.getMapAreaData().iterator().next();
ROUGH_mapAreaData.setCalculatedStats(createMockCalculatedStats(doubleToBigDecimal(15.43),
DataQuality.ROUGH.getMinNumAnalysed(), DataQuality.GOOD.getMinNumAnalysed()));
//ROUGH_mapAreaData = dataRepository.save(ROUGH_mapAreaData); - WORKS if this is used
ROUGH_mapArea = mapAreaRepository.save(ROUGH_mapArea);
Turns out I had to specify a targetEntity to make it all work nicely together for Set<MapAreaData> in MapArea class as follows:
#OneToMany(targetEntity = MapAreaData.class, cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "data")
private Set<MapAreaData> mapAreaData;
I am having a particular issue when trying to save a collection of objects with hibernate. It seems that when I have more than one object of the same type, hibernate fails to generate the identifier, so I get a org.hibernate.NonUniqueObjectException .
Example:
App1 --> urls
{strApplicationId:1;URLTypeEntity{strCode:1,strDescription:Reply},strURL:www.address1.com},
{strApplicationId:1;URLTypeEntity{strCode:1,strDescription:Reply},strURL:www.address2.com},
{strApplicationId:1;URLTypeEntity{strCode:2,strDescription:Home},strURL:www.address3.com}
If I do not have two URLs with the same URLTypeEntity on the collection, the error is not triggered
#Entity
#Table(name = "tbl_urls")
public class URLEntity
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="intCode")
private Integer intCode;
private String strApplicationID;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "intType", referencedColumnName = "intCode")
private URLTypeEntity objURLType;
private String strURL;
}
#Entity
#Table(name = "tbl_applications")
public class ApplicationEntity
{
#OneToMany(cascade = CascadeType.ALL, mappedBy = "strApplicationID")
private List<URLEntity> colURLs;
}
ApplicationEntity must also have an id.
The solution was changing the CascadeType from ALL To Merge
#OneToMany(cascade = CascadeType.ALL, mappedBy = "strApplicationID")
Changed To
#OneToMany(cascade = CascadeType.MERGE, mappedBy = "strApplicationID")
Suppose we have entity A that contains a list of entities with type B (with lazy initialization).
Entity B has one BLOB field and some other, that doesn't contain much data.
How can I, using hibernate criteria query, get entity A with it's fields and each A-entity with list of Bs, but every B-entity without the BLOB field ?
Also, I do not want to extract As and iterate them to get Bs. (I now, how to use 'Projections' to extract just Bs with required fields).
Here is some code sample:
A-entity:
#Entity
#Table(name = "A")
public class A implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
#OneToMany(mappedBy = "order", fetch = FetchType.LAZY)
#Cascade(CascadeType.ALL)
private List<B> list = new LinkedList<>();
}
B-entity:
#Entity
#Table(name = "B")
public class B implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
#Column(name = "data", nullable = false)
#Lob
private byte[] data;
#ManyToOne(targetEntity = A.class, fetch = FetchType.EAGER)
#JoinColumn(name = "A_id", nullable = false)
private A a;
}
I have 3 entities and one entity for linking these three entities:
#Entity
public class A {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "a", fetch = FetchType.LAZY)
private List<ABCXref> abcXref = new ArrayList<ABCXref>();
// getters/setters
}
#Entity
public class B {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "b", fetch = FetchType.LAZY)
private List<ABCXref> abcXref = new ArrayList<ABCXref>();
// getters/setters
}
#Entity
public class C {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "c", fetch = FetchType.LAZY)
private List<ABCXref> abcXref = new ArrayList<ABCXref>();
// getters/setters
}
#Entity
public class ABCXref {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#JoinColumn(name = "A_ID", nullable = true)
#ManyToOne(optional = true, fetch = FetchType.LAZY)
private A a;
#JoinColumn(name = "B_ID")
#ManyToOne(optional = false, fetch = FetchType.LAZY)
private B b;
#JoinColumn(name = "C_ID")
#ManyToOne(optional = false, fetch = FetchType.LAZY)
private C c;
}
The question is: does all instances of A, B and C share the same list of ABCXref's? What if I add new ABCXref to A instance? Does this ABCXref appear in list of ABCXrefs of B and C?
I.e. will this code output true?
println(b.getAbcXref().size()); //0
c.getAbcXref().add(new ABCXref(a, b, c));
println(b.getAbcXref().size() == 1) //is `true` or `false`?