I have the following class structure:
public class Container {
private Set<Child> childs = new HashSet<>();
}
public class Child {
String name;
String value;
String param;
}
Because of Container class is going to be single in application (just wrapper on collections that may be updated), I would like to store it in single table, e.g."childs" with columns
id | name | value | param
Is it possible to make such mapping in order to use Container when fetch or
store it through hb session with corresponding updates to childs collection:
getSession().saveOrUpdate(container)
And if so, what mapping should be in that case?
UPD: I was going to have 2 classes mapped to single table
After some research I ended up with such solution:
#Entity
#Table(name = "childs")
public class Container {
#Id
//optional your id strategy
#GeneratedValue(strategy = GenerationType.AUTO, generator = "xyz")
Long id;
#ElementCollection(fetch = FetchType.EAGER)
#CollectionTable(name="childs", joinColumns=#JoinColumn(name="id"))
private Set<Child> childs = new HashSet<>();
}
#Embeddable
public class Child {
String name;
String value;
String param;
}
table structure - childs(id, name, value, param)
im not sure about the getSession().saveOrUpdate(container) , but the mapping for you problem should look something like this.
#Entity
public class Container {
#ManyToOne
private Set<Child> childs = new HashSet<>();
}
#Entity
public class Child {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
Long id;
String name;
String value;
String param;
}
Related
I created the following models:
"Vendor"
"PickupStation"
And both of them have a OneToMany Relationship to a composite Key
"PickupStationVendorDetails" which has an embedded Id "PickupStationVendorKey"
It works to save the "PickupStationVendorDetails" with the corresponding Vendor and PickupStation but when I want to fetch them from e.g the Vendor nothing is found.
Vendor.java
#Entity
public class Vendor {
...
#OneToMany(mappedBy = "vendor")
private Set<PickupStationVendorDetails> pickupStations;
}
PickupStation.java
#Entity
public class PickupStation {
#OneToMany(mappedBy = "pickupStation")
private Set<PickupStationVendorDetails> vendors;
}
PickupStationVendorDetails.java
#Entity
public class PickupStationVendorDetails {
#EmbeddedId
private PickupStationVendorKey id;
#ManyToOne
#MapsId("vendorId")
#JoinColumn(name = "vendor_id")
private Vendor vendor;
#ManyToOne
#MapsId("pickupStationId")
#JoinColumn(name = "pickup_station_id")
private PickupStation pickupStation;
}
PickupStationVendorKey.java
#Embeddable
public class PickupStationVendorKey implements Serializable {
#Column(name = "vendor_id", columnDefinition = "BINARY(16)")
private UUID vendorId;
#Column(name = "pickup_station_id")
private Long pickupStationId;
public PickupStationVendorKey() {
}
public PickupStationVendorKey(UUID vendorId, Long pickupStationId) {
this.vendorId = vendorId;
this.pickupStationId = pickupStationId;
}
....
}
How I persist the entities:
At first I create the embeddedID and save the details via repository:
PickupStationVendorDetails pickupStationVendorDetails = new PickupStationVendorDetails();
pickupStationVendorDetails.setVendor(vendor);
pickupStationVendorDetails.setPickupStation(pickupStation);
pickupStationVendorDetails.setDeliveryDays(relationship.getDeliveryDays());
PickupStationVendorKey embeddedId = new PickupStationVendorKey(vendor.getId(),pickupStation.getId());
pickupStationVendorDetails.setId(embeddedId);
PickupStationVendorDetails d = pickupStationVendorDetailsRepository.save(pickupStationVendorDetails);
Afterwards I add them to the Set<> of the corresponding Entities and save them too.
vendor.getPickupStations().add(d);
pickupStation.getVendors().add(d);
vendorService.save(vendor);
pickupStationRepository.save(pickupStation);
And when I try to call vendor.getPickupStations() there seems to be no relationship.
Except I call pickupStationVendorDetailsRepository.findAll() the composite Key is correctly persisted and saved, and from there on I would be able to get the PickupStation and the Vendor. But that's not how it should work I guess.
Am I missing something?
I have the following problem: I have three connected classes. I have annotated them but I am getting wrong results (described below):
#Entityd
#Table(name = "ClassA")
public class ClassA{
#Id
#GeneratedValue
private Long id = 0L;
...
#OneToMany(fetch = FetchType.EAGER,cascade=CascadeType.ALL)
#Fetch(FetchMode.SELECT)
#Column(name = "ClassBList")
private List<ClassB> listB;
...
}
#Entity
#Table(name="ClassB")
public class ClassB {
#Id
#GeneratedValue
private Long id = 0L;
...
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#Fetch(FetchMode.SELECT)
#Column(name = "ClassCList")
private List<ClassC> listC;
...
}
#Entity
#Table(name="ClassC")
public class ClassC {
#Id
#GeneratedValue()
private Long id = 0L;
...
#ElementCollection
private List<String> listD;
...
}
When I work with this structure for the first ClassA I create,save and load everything is ok. For a new instance of ClassA which I save to repo and load again, I suddenly have the strings of the first ClassA in listD.
The result I need is that every class is "independently" saved. So the collections of each class should hold unique (each one with its own id and sublists) objects.
What would be the best way (annotations) to model this classes in Java 8 with Spring Boot 2.2.0.M5 and javax.persistence-api 2.2 ?
EDIT:
I have now removed class B and rewrote classA to:
#Entity
#Table(name = "ClassA")
public class ClassA{
#Id
#GeneratedValue
private Long id = 0L;
...
#OneToMany(fetch = FetchType.LAZY, orphanRemoval = true, cascade = CascadeType.ALL)
#MapKey(name = "type")
private Map<String,Set<ClassC>> classCmap;
...
}
This is giving me an error like:
org.hibernate.AnnotationException: Use of #OneToMany or #ManyToMany targeting an unmapped class
How can I model/solve/annotate this?
If you don't need to query data based on listD, I would suggest to keep the list as text in the database and use a converter:
#Converter
public class ListDConverter implements AttributeConverter<List<String>, String> {
private ObjectMapper objectMapper = new ObjectMapper();
#Override
public String convertToDatabaseColumn(List<String> listD) {
try {
return objectMapper.writeValueAsString(listD);
} catch(IOException e){
return null;
}
}
#Override
public List<String> convertToEntityAttribute(String stringListD) {
if(stringListD == null){
return Collections.emptyList();
}
try {
return objectMapper.readValue(stringListD, new TypeReference<List<String>>() {});
}catch(IOException e){
return Collections.emptyList();
}
}
}
and in your ClassC entity class :
#Convert(converter = ListDConverter.class)
private List<String> listD;
Why do I like this approach :
No extra table and joins => better performance
Easier to read listD in the database
#ElementCollection describes a table. So your code is probably creating a "listD" table with one column of type string, with no primary key.
Also, do you really want to use the SELECT fetch mode? That's going to generate 1 + b + b*c queries when you could just implement your data as sets (since you have unique identifiers) and use JOIN, which would result in one and only one query.
See this site for an explanation on how to use #ElementCollection.
I am designing two entities, one called Country and one called CountryDetail. From the perspective of tables, the COUNTRY table will be the parent table, and the COUNTRY_DETAIL table will be the child table. In the COUNTRY table, there will be a unique attribute called COUNTRY_CODE (note this is not a primary key; the primary key will be a numeric sequence based value). This code will be a foreign key to connect to the child table, and in this child table, each COUNTRY_CODE from the parent table will have 3 entries to represent the name of the country in 3 different languages. Following are the entity classes:
Country.java
#Entity
public class Country
{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "COUNTRY_ID")
private long id;
#Column(name="COUNTRY_CODE", nullable = false, unique = true)
private String countryCode;
/*public getters*/
}
CountryDetail.java
#Entity
public class CountryDetail
{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "COUNTRY_DETAIL_ID")
private long id;
#ManyToOne
#JoinColumn(name="COUNTRY_CODE", referencedColumnName = "COUNTRY_CODE")
private Country country;
#Column(nullable = false)
private String languageCode;
#Column(nullable = false, unique = true)
private String countryNameInLanguage;
/*public getters*/
}
My question is, how can I write a custom "findBy..." interface method inside an extension of JpaRepository that is typed to a Country that would return me a collection of CountryDetail elements that match an input parameter for the languageCode attribute of the CountryDetail class?
public interface CountryRepository extends JpaRepository<Country, Long>
I know how to do it if the repository was typed to CountryDetail instead of Country, but I would like to know how to do it going via the parent entity rather than via the child entity directly, even though the input parameter (languageCode) exists only in the child entity.
Thank you.
It is possible but complicated : Please see an example below :
Parent :
#Entity
#Table(name="PARENT")
public class Parent {
#Id
#Column(name="PARENT_ID")
private int parentId;
#Column(name="PARENT_NAME")
private String parentName;
#OneToOne(cascade=CascadeType.ALL,fetch=FetchType.EAGER)
private Child child;
}
Child :
#Entity
#Table(name="CHILD")
public class Child {
#Id
#Column(name="CHILD_ID")
private int childId;
#Column(name="CHILD_NAME")
private String childName;
#OneToOne(cascade=CascadeType.ALL,fetch=FetchType.EAGER)
private Parent parent;
}
Define an interface to load child entity from parent repository :
ChildEntity:
public interface ChildEntity {
#Value("#{target.child.childId}")
int getChildId() ;
#Value("#{target.child.childName}")
String getChildName();
}
Parent Repository :
public interface ParentRepository extends JpaRepository<Parent, Integer> {
public ChildEntity findByParentName(String parentName);
}
Test class:
ChildEntity chi=rep.findByParentName("<<NAME>>");
System.out.println(chi.getChildId()+" "+chi.getChildName());
Output:
CHILD ID 1000 CHILD NAME child1
I have an entity, that i'd like to join OneToOne with a table with a composite key (Omitting getters/setters):
#Entity
#Table(name = "parent")
public class Parent {
#Id
private String parentId;
#Column(name = "data")
private String data;
#OneToOne
private Child child;
}
And:
#Entity
#IdClass(ChildKey.class)
#Table(name = "child")
public class Child{
#Id
private String parentId;
#Id
private String username;
#Column(name = "data")
private String childData;
}
public class ChildKey implements Serializable {
private String parentId;
private String username;
}
Parent does not have a notion of the 'username' field in the Child entity. I need to pass this in as criteria. In the DB, the primary key of child is on parentId and username.
If I don't specify a JoinColumn, hibernate attempts to map using fields child_username and child_parentId. If I specify only one Joincolumn, I get a broken mapping. If I specify both JoinColumns, I have no column on parent to specify.
How can I map this class and pass in the username as criteria? (it is coming from authentication data) Or how can I do this in a different way if I'm off track.
You might be able to use a Derived Identity.
The Parent class would remain the same; but you would specify a #OneToOne mapping back to the child's parent and the Child and ChildKey classes would look like this:
#Entity
#IdClass(ChildKey.class)
#Table(name = "child")
public class Child{
#Id
#OneToOne(mappedBy="child")
private Parent parent;
#Id
private String username;
#Column(name = "data")
private String childData;
}
public class ChildKey implements Serializable {
private String parent; // name matches name of the #Id field and type matches type of Parent #Id field
private String username; // name and type match those of the #Id field
}
Derived identity is discussed in JPA 2.1 spec, section 2.4.1.
What I ended up doing was defining a #Filter on the Child class, like so:
#Entity
#IdClass(ChildKey.class)
#Table(name = "child")
#FilterDef(name = "usernameFilter", parameters = {
#ParamDef( name = "username", type="string")
})
public class Child { ... }
On the Parent class, I annotated the collection with a reference to the filter:
#OneToMany(fetch = FetchType.EAGER)
#JoinColumn(name = "parentId")
#Filter(name="usernameFilter", condition = "username = :username")
private List<Child> children;
Finally, in my DAO, I parameterized the filter by name like so:
Filter filter = currentSession().enableFilter("usernameFilter");
filter.setParameter("username", user.getUsername());
Doing this resulted in the exact SQL I had in mind, which is an additional clause in the JOIN criteria with a variable:
SELECT
...
FROM
parent this_
LEFT OUTER JOIN
child child_ ON this_.parentId = child_.parentId
AND child_.username = ?
I might not have been clear about what end result I was looking for in my original question. Posting this answer in case it helps someone else.
I have the following two entities in the DB ( structure is fixed ) which I am trying to map using JPA Annotations and EBEAN is ORM.
I have the following beans:
class Item {
public Long id;
public String name;
public Consignee intermediate;
public Consignee ultimate;
}
class Consignee {
public Long id;
public String name;
public String address;
public Item item;
}
And their corresponding tables:
Item
----
id
name
Consignee
---------
id:
name
address
item_id
type: [1,2] / 1: intermediate, 2:ultimate
the main entity is ITEM although the relationship is mapped from the consignee side.
How can I mapped this using the Annotations so that the consignees ( ultimate, intermediate ) are loaded when I fetch the Item object from DB ?
Could you please point me to the right direction
The two tables your are trying to map to Ebean are called Entity Models and the relation between Entity Consignee to Item is a One to Many Relationship.
Such relation can be mapped with a #OneToMany annotation on the Consignee side, and with an #ManyToOne on the Item side.
Also the field type of Consignee can be mapped with an Enumeration persisted as integer, and the remaining fields can be mapped via #Column annotation.
A possible implementation of your requirements could be something like:
public enum CONSIGNEE_TYPE {
INTERMEDIATE,
ULTIMATE
}
#Entity
#Table(name = "Consignee")
public class Consignee extends Model {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
public Long id;
public String name;
public String address;
#Enumerated(EnumType.ORDINAL)
public CONSIGNEE_TYPE type;
#Column(name="item_id")
#OneToMany(cascade = CascadeType.ALL, mappedBy = "consignee", fetch = FetchType.EAGER)
public List<Item> item = new ArrayList<Item>();
//TODO: Generate Constructors/Getters/Setters
}
#Entity
#Table(name = "Item")
public class Item extends Model {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
public Long id;
public String name;
#ManyToOne(optional = false, fetch = FetchType.EAGER)
public Consignee consignee = new Consignee();
//TODO: Generate Constructors/Getters/Setters
}
I removed the ambiguation with the ultimate and intermediate consignee type from the class Item since you can store the type of the consignee on the consignee itself.
Hope this helps.
As a very nice resource for the future, I recommend you to read the Unit Tests available on the source code of the Ebean itself. It ain't pretty but it helped me a lot!