Hibernate - link table with additional columns - saving in one transaction - java

I am learning Hibernate and just read the chapter "7.2.3 Adding columns to join tables" of the "Java Persistance with Hibernate" book. My goal is to save Item, Category and CategorizedItem in one transaction.
There is a constructor there (page 305):
public CategorizedItem(String username, Category category, Item item) {
// Set fields
this.username = username;
this.category = category;
this.item = item;
// Set identifier values
this.id.categoryId = category.getId();
this.id.itemId = item.getId();
// Guarantee referential integrity
category.getCategorizedItems().add(this);
item.getCategorizedItems().add(this);
}
It accepts category and item objects. If I create a Category and an Item and want to connect them with this technique, they obviously have to be persisted BEFORE, as category.getId() and item.getId() return null.
Is there "a trick in the Hibernate bag" that can cascade the saving of a join table? The join table have additional columns. I want to save all three objects in the onSuccess handler in my web page controller. All three entities or none of them must be inserted.

You said
My goal is to save Item, Category and CategorizedItem in one transaction
Is there a trick in the Hibernate bag that can cascade the saving of a join table ?
Yes, use MutableInt
#Entity
public class CategorizedItem implements Serializable {
private CategorizedItemId categorizedItemId;
private String userName;
private Category category;
private Item item;
/**
* required no-arg constructor
*/
public CategorizedItem() {}
public CategorizedItem(CategorizedItemId categorizedItemId) {
this.categorizedItemId = categorizedItemId;
}
public CategorizedItem(String userName, Category category, Item item) {
this.userName = userName;
this.categorizedItemId = new CategorizedItemId(category.getIdAsMutableInt(), item.getIdAsMutableInt());
}
#EmbeddedId
public CategorizedItemId getCategorizedItemId() {
return this.categorizedItemId;
}
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name="CATEGORY_ID", insertable=false, updateable=false)
public Category getCategory() {
return this.category;
}
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name="ITEM_ID", insertable=false, updateable=false)
public Item getItem() {
return this.item;
}
// setter's goes here
/**
* It MUST implements Serializable
* It MUST overrides equals and hashCode method
* It MUST has a no-arg constructor
*
* Hibernate/JPA 1.0 does not support automatic generation of compound primary key
* You SHOULD set up manually
*/
#Embeddable
public static class CategorizedItemId implements Serializable {
private MutableInt categoryId = new MutableInt(-1);
private MutableInt itemId = new MutableInt(-1);
/**
* required no-arg constructor
*/
public CategorizedItemId() {}
public CategorizedItemId(MutableInt categoryId, MutableInt itemId) {
this.categoryId = categoryId;
this.itemId = itemId;
}
#Column(name="CATEGORY_ID", updateable=false, nullable=false)
public Integer getCategoryId() {
return this.categoryId.intValue();
}
public void setCategoryId(Integer categoryId) {
return this.categoryId.setValue(categoryId);
}
#Column(name="ITEM_ID", updateable=false, nullable=false)
public Integer getItemId() {
return this.itemId.intValue();
}
public void setItemId(Integer itemId) {
return this.itemId.setValue(itemId);
}
// getter's and setter's
#Override
public boolean equals(Object o) {
if(!(o instanceof CategorizedItemId))
return null;
finalCategorizedItemId other = (CategorizedItemId) o;
return new EqualsBuilder().append(getCategoryId(), other.getCategoryId())
.append(getItemId(), other.getItemId())
.isEquals();
}
#Override
public int hashCode() {
return new HashCodeBuilder().append(getCategoryId())
.append(getItemId())
.toHashCode();
}
}
}
Here goes Category
#Entity
public class Category implements Serializable {
public MutableInt id = new MutableInt(-1);
private List<CategorizedItem> categorizedItemList = new ArrayList<CategorizedItem>();
#Transient
public MutableInt getIdAsMutableInt() {
return this.id;
}
#Id
#GeneratedValue
public Integer getId() {
return this.id.intValue();
}
public void setId(Integer id) {
return this.id.setValue(id);
}
#OneToMany(mappedBy="category")
#JoinColumn(name="CATEGORY_ID", insertable=false, updateable=false)
#Cascade(CascadeType.SAVE_UPDATE)
public List<CategorizedItem> getCategorizedItemList() {
return categorizedItemList;
}
// setter's
/**
* Use this method when you have a saved Item
*/
public void addCategorizedItem(CategorizedItem categorizedItem) {
categorizedItem.setCategorizedItemId(new CategorizedItemId(getIdAsMutableInt(), categorizedItem.getItem().getIdAsMutableInt()));
categorizedItem.setCategory(this);
getCategorizedItemList().add(categorizedItem);
}
}
And Item
#Entity
public class Item implements Serializable {
public MutableInt id = new MutableInt(-1);
private List<CategorizedItem> categorizedItemList = new ArrayList<CategorizedItem>();
#Transient
public MutableInt getIdAsMutableInt() {
return this.id;
}
#Id
#GeneratedValue
public Integer getId() {
return this.id.intValue();
}
public void setId(Integer id) {
return this.id.setValue(id);
}
#OneToMany(mappedBy="item")
#JoinColumn(name="ITEM_ID", insertable=false, updateable=false)
#Cascade(CascadeType.SAVE_UPDATE)
public List<CategorizedItem> getCategorizedItemList() {
return this.categorizedItemList;
}
// setter's
/**
* Use this method when you have a saved Category
*/
public void addCategorizedItem(CategorizedItem categorizedItem) {
categorizedItem.setCategorizedItemId(new CategorizedItemId(getIdAsMutableInt(), categorizedItem.getCategory().getIdAsMutableInt()));
categorizedItem.setItem(this);
getCategorizedItemList().add(categorizedItem);
}
}
Now because you need categoryId and itemId before saving CategorizedItem, do as follows
Category category = new new Category();
Item item = new Item();
session.save(new Category());
session.save(new Item());
session.save(new CategorizedItem(userName, category, item));
Notice the cascading just works when you have either a saved Category or a saved Item. Otherwise, you need to follow the approach shown above

Related

How to map a class that contains a list of objects of various type to a collection of tables with Hibernate?

I have a class that is called Element and it is subclassed by User and Entity that have some common fields like value, id and uuid and some others that are not common..
#Entity
class Element {
#Id #GeneratedValue
private Long id;
private String uuid;
private String value;
}
#javax.persistence.Entity
class Entity extends Element {
private String description;
}
#Entity
class User extends Element {
private String password;
}
I also have a class Information:
#Entity
class Information {
#Id #GeneratedValue
private Long id;
private List<Element> elements;
private String information_field1;
private String information_field2;
}
elements can contain Users and Entities
How would you suggest I should map that with Hibernate..
I was thinking that in the database I could have those tables
Element
----------------
information_id, user_id, entity_id, order
0, null, 10, 0
0, 12, null, 1
Information
----------------
id, information_field1, information_field2
0, "some value", "some other value"
That represents Information with id 0, having first (order = 0) Element being an Entity with id 10 and second (order = 1) Element being a User with id 12.
I don't mind a completely different db design as long as it doesn't lose information but the class structure isn't possible to be changed. I know how to make it work with classes that are exactly the same as the rows of the tables I suggested. I am curious if Hibernate annotations are sophisticated enough so when I store Information it will insert the appropriate rows, ids and order in the two tables.
Thanks!
Hibernate documentation https://docs.jboss.org/hibernate/orm/3.6/reference/en-US/html/collections.html#collections-mapping explains how to map collections. You must use #OneToMany and #JoinTable annotations.
#Entity
class Information {
#Id #GeneratedValue
private Long id;
#OneToMany
#JoinTable(
name="INFORMATION_ELEMENTS",
joinColumns = #JoinColumn( name="INFORMATION_ID"),
inverseJoinColumns = #JoinColumn( name="ELEMENT_ID")
)
private List<Element> elements;
private String information_field1;
private String information_field2;
}
This will create a table INFORMATION_ELEMENTS that contains the Information to Element mappings. You don't need to maintain INFORMATION_ID in the Element table.
So this is what worked for me
#javax.persistence.Entity
#Inheritance(strategy = InheritanceType.JOINED)
public abstract class Element {
#Id
#GeneratedValue
#Column(name = "element_id")
private Long id;
#Column(name = "uuid", nullable = false)
private String uuid;
#Column(name = "value")
private String value;
public Element(String value) {
this.value = value;
}
public Element() {
}
#PrePersist
public void initUuid() {
this.uuid = UUID.randomUUID().toString();
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUuid() {
return uuid;
}
public void setUuid(String uuid) {
this.uuid = uuid;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
#PrimaryKeyJoinColumn(name="element_id")
public class Entity extends Element {
private String description;
public Entity() {
super();
}
public Entity(String value, String description) {
super(value);
this.description = description;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}
#PrimaryKeyJoinColumn(name="element_id")
public class User extends Element {
public User() {
super();
}
public User(String value) {
super(value);
}
}
#javax.persistence.Entity
public class Information {
#Id
#GeneratedValue
#Column(name = "information_id")
private Long id;
private String uuid;
private String value;
private String innerText;
#ManyToMany(cascade = {CascadeType.MERGE,CascadeType.REFRESH} )
private List<Element> elements;
public Information() {
}
public Information(String innerText, List<Element> elements) {
this.innerText = innerText;
this.elements = elements;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUuid() {
return uuid;
}
public void setUuid(String uuid) {
this.uuid = uuid;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public String getInnerText() {
return innerText;
}
public void setInnerText(String innerText) {
this.innerText = innerText;
}
public List<Element> getElements() {
return elements;
}
public void setElements(List<Element> elements) {
this.elements = elements;
}
}
I then extend the CrudRepository interfaces:
public interface InformationDao extends CrudRepository<Information,Long> {
List<Information> findAll();
}
public interface ElementDao extends CrudRepository<Element, Long> {
List<Element> findAll();
Element findByValue(String value);
Element findById(Long id);
}
And for testing purposes I have this Service.
public class InformationService {
#Autowired
ElementDao elementDao;
#Autowired
InformationDao infoDao;
#Transactional
public void init() {
List<Element> elements = new ArrayList<>();
Element element1 = new Entity("test", "description");
Element element2 = new User("test2");
Element element3 = new Entity("test3", "description");
Element element4 = new User("test4");
elements.add(element1);
elements.add(element2);
elements.add(element3);
elements.add(element4);
elementDao.save(elements);
Information info = new Information("some text", elements);
infoDao.save(info);
List<Element> elements2 = new ArrayList<>();
Element element5 = elementDao.findById(element1.getId());
Element element6 = elementDao.findById(element2.getId());
Element element7 = elementDao.findById(element3.getId());
Element element8 = elementDao.findById(element4.getId());
elements2.add(element5);
elements2.add(element6);
elements2.add(element7);
elements2.add(element8);
elementDao.save(elements2);
Information info2 = new Information("# something else #", elements2);
//failTransaction();
infoDao.save(info2);
}
public void failTransaction() {
throw new RuntimeException("transaction failed");
}
}
Keep in mind that due to
#ManyToMany(cascade = {CascadeType.MERGE,CascadeType.REFRESH} )
private List<Element> elements;
on Information, all Elements need to be already pre-stored in the database. I couldn't make it to work with CascadeType.ALL that stores children Element all the time when the parent element is stored. I was getting detached entity passed to persist when I was trying to store info2

Spring JPA - Data integrity relationships

I'm new to Java and even more newer to Spring (Boot and JPA) but I was curious, I'm trying to debug an issue that says, "No identifier specified for entity".
For illustartion purposes, I've created the following tables from this diagram:
Originally, there was a M:N relationship between the user and vehicle table, so I created an associative entity (UserVehicleAsso) to split the two up. I was following this guide on M:N mapping in Java, http://viralpatel.net/blogs/hibernate-many-to-many-annotation-mapping-tutorial/
For the most part, it was pretty straight forward but my question is, within the associative entity (UserVehicleAsso), do I have to use the #Id annotation for each of the foreign keys? I assume that I didn't need to because those were automatically generated from each of the respective tables.
Let me know your thoughts or comments, thanks.
Also, below is the code that I used to generate these models:
For the User table/class:
#Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int userId;
private String fName;
private String lName;
#ManyToMany(cascade = {CascadeType.ALL})
#JoinTable(name="userVehicleAsso",
joinColumns={#JoinColumn(name="userID")},
inverseJoinColumns={#JoinColumn(name="vehicleID")})
private Set<Vehicle> vehicles = new HashSet<Vehicle>();
//constructor
protected User() {}
public int getUserId() {
return userId;
}
public void setUserId(int userId) {
this.userId = userId;
}
public String getFName() {
return fName;
}
public void setFName(String fName) {
this.fName = fName;
}
public String getLName() {
return lName;
}
public void setLName(String lName) {
this.lName = lName;
}
public Set<Vehicle> getVehicles() {
return vehicles;
}
public void setVehicles(Set<Vehicle> vehicles) {
this.vehicles = vehicles;
}
#Override
public String toString() {
return getFName() + "," + getLName();
}}
For the Vehicle table/class:
#Entity
public class Vehicle {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int vehicleId;
private String brand;
private String model;
//foreign key mappings
//mapping with associative
#ManyToMany(mappedBy="vehicles")
private Set<User> users = new HashSet<User>();
//constructors
protected Vehicle() {}
public Vehicle(int id) {
this.vehicleId = id;
}
public Vehicle (String brand, String model) {
this.brand = brand;
this.model = model;
}
/* public Vehicle() {
}*/
public int getVehicleId() {
return vehicleId;
}
public Set<User> getUsers() {
return users;
}
public void setUsers(Set<User> users) {
this.users = users;
}
public void setVehicleId(int vehicleId) {
this.vehicleId = vehicleId;
}
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public String getModel() {
return model;
}
public void setModel(String model) {
this.model = model;
}
#Override
public String toString() {
// + setBodyType() + "," +
return getBrand() + "," + getModel();
}
}
And then finally, my associtive table/class:
#Entity
public class UserVehicleAsso{
private int userID;
private int vehicleID;
public int getUserID() {
return userID;
}
public void setUserID(int userID) {
this.userID = userID;
}
public int getVehicleID() {
return vehicleID;
}
public void setVehicleID(int vehicleID) {
this.vehicleID = vehicleID;
}
}
In my opinion, it's not necessary to have an Entity class for the middle table in your case. The table will be generated automatically if configured correctly. In this table, there would not be column ID, only two columns with userID and vehicleID data.
Now, if your middle table has more than what are needed to establish the M:N relationship, then your middle Entity class is needed, and the ID of it, too. For example, if this class is intended to store the time stamp every time a relationship is established, you have to:
Create this Entity class,
Give it an ID field with proper generation strategy,
Map the time stamp with a field with adequate type, annotation/XML mapping and so on.
This part of JPA/Hibernate have confused me a lot and I used to get into them. If my memory serves me well this is the proper/perfect way how things should work.
You can specify a composite primary key class that is mapped to multiple fields or properties of the entity.
Here are sample codes:
public class ActivityRegPK implements Serializable {
private int activityId;
private int memberId;
public int getActivityId() {
return activityId;
}
public void setActivityId(int activityId) {
this.activityId = activityId;
}
public int getMemberId() {
return memberId;
}
public void setMemberId(int memberId) {
this.memberId = memberId;
}
}
associtive table/class:
#IdClass(ActivityRegPK.class)
#Entity
#Table(name="activity_reg")
#NamedQuery(name="ActivityReg.findAll", query="SELECT a FROM ActivityReg a")
public class ActivityReg implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name="activity_id")
private int activityId;
#Temporal(TemporalType.TIMESTAMP)
#Column(name="ins_date")
private Date insDate;
#Id
#Column(name="member_id")
private int memberId;
}
Activity.class
#Entity
#NamedQuery(name="Activity.findAll", query="SELECT a FROM Activity a")
public class Activity implements Serializable {
// some attributes
}

How to model a one-to-one relationship in JPA when the "parent" table has a composite PK?

While there is plenty of information around on how to model, in JPA (2), a one-to-one relationship OR an entity having a natural key, I haven't been able to find a clear / simple answer to how to model the situation where we have both, i.e. a one-to-one relationship where the parent table has a natural key. It could obviously be that I might have missed such a tutorial; if so, pointing me to one could also be the answer.
And, as many times with JPA and noobs such as I, the moment one needs a bit more than the most basic model, one can quickly hit the wall.
Hence, considering the following DB model:
What would be the corresponding JPA-annotated object model? (I'm sparing you guys of the things I've tried since I don't want to influence the answer...)
Performance recommendations are also welcome (e.g. "a one-to-many could perform faster", etc.)!
Thanks,
The composite identifier is built out of two numerical columns so the mapping looks like this:
#Embeddable
public class EmployeeId implements Serializable {
private Long companyId;
private Long employeeId;
public EmployeeId() {
}
public EmployeeId(Long companyId, Long employeeId) {
this.companyId = companyId;
this.employeeId = employeeId;
}
public Long getCompanyId() {
return companyId;
}
public Long getEmployeeId() {
return employeeId;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof EmployeeId)) return false;
EmployeeId that = (EmployeeId) o;
return Objects.equals(getCompanyId(), that.getCompanyId()) &&
Objects.equals(getEmployeeId(), that.getEmployeeId());
}
#Override
public int hashCode() {
return Objects.hash(getCompanyId(), getEmployeeId());
}
}
The parent class, looks as follows:
#Entity(name = "Employee")
public static class Employee {
#EmbeddedId
private EmployeeId id;
private String name;
#OneToOne(mappedBy = "employee")
private EmployeeDetails details;
public EmployeeId getId() {
return id;
}
public void setId(EmployeeId id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public EmployeeDetails getDetails() {
return details;
}
public void setDetails(EmployeeDetails details) {
this.details = details;
}
}
And the child like this:
#Entity(name = "EmployeeDetails")
public static class EmployeeDetails {
#EmbeddedId
private EmployeeId id;
#MapsId
#OneToOne
private Employee employee;
private String details;
public EmployeeId getId() {
return id;
}
public void setId(EmployeeId id) {
this.id = id;
}
public Employee getEmployee() {
return employee;
}
public void setEmployee(Employee employee) {
this.employee = employee;
this.id = employee.getId();
}
public String getDetails() {
return details;
}
public void setDetails(String details) {
this.details = details;
}
}
And everything works just fine:
doInJPA(entityManager -> {
Employee employee = new Employee();
employee.setId(new EmployeeId(1L, 100L));
employee.setName("Vlad Mihalcea");
entityManager.persist(employee);
});
doInJPA(entityManager -> {
Employee employee = entityManager.find(Employee.class, new EmployeeId(1L, 100L));
EmployeeDetails employeeDetails = new EmployeeDetails();
employeeDetails.setEmployee(employee);
employeeDetails.setDetails("High-Performance Java Persistence");
entityManager.persist(employeeDetails);
});
doInJPA(entityManager -> {
EmployeeDetails employeeDetails = entityManager.find(EmployeeDetails.class, new EmployeeId(1L, 100L));
assertNotNull(employeeDetails);
});
doInJPA(entityManager -> {
Phone phone = entityManager.find(Phone.class, "012-345-6789");
assertNotNull(phone);
assertEquals(new EmployeeId(1L, 100L), phone.getEmployee().getId());
});
Code available on GitHub.

Map table without bean

I'm not sure if the title is right... But here is my problem
This is my bean class
#Entity
#Table(name = "Exercise")
public class Exercise {
private IntegerProperty exerciseID;
private ObjectProperty<String> name;
private ObjectProperty<ExerciseCategory> category;
private ObservableList<Parameter> parameters;
public Exercise(int exerciseID, String name, ExerciseCategory category){
this.exerciseID = new SimpleIntegerProperty(exerciseID);
this.name = new SimpleObjectProperty<>(name);
this.category = new SimpleObjectProperty<>(category);
parameters = FXCollections.observableArrayList();
}
public Exercise(){
this(0,null, null);
}
public Exercise(String name, ExerciseCategory category){
this(0, name, category);
}
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
public int getExerciseID() {
return exerciseID.get();
}
public IntegerProperty exerciseIDProperty() {
return exerciseID;
}
public void setExerciseID(int exerciseID) {
this.exerciseID.set(exerciseID);
}
#Column(name = "name", nullable = false)
public String getName() {
return name.get();
}
public ObjectProperty<String> nameProperty() {
return name;
}
public void setName(String name) {
this.name.set(name);
}
#OneToOne
public ExerciseCategory getCategory() {
return category.get();
}
public ObjectProperty<ExerciseCategory> categoryProperty() {
return category;
}
public void setCategory(ExerciseCategory category) {
this.category.set(category);
}
public ObservableList<Parameter> getParameters() {
return parameters;
}
public void setParameters(ObservableList<Parameter> parameters) {
this.parameters = parameters;
}
}
One exercise can have one more parameters. This mapping is saved in the ExerciseParameter table, which looks like this:
ExerciseParameter
ExerciseID int(11) PK
ParameterID int(11) PK
My question is, how do I map this in the Exercise class? Because I don't want to make a ExerciseParamter class...
Thank you!
It is a many-to-many relationship. You can use #ManyToMany annotion to do that.
Add following Annotation into getParameters method of Employee Entity
#ManyToMany(cascade = {CascadeType.ALL})
#JoinTable(name="ExerciseParameter",
joinColumns={#JoinColumn(name="ExerciseID ")},
inverseJoinColumns={#JoinColumn(name="ParameterID")})
And also add following Annotation into getEmployees method of Parameter entity. (You have not presented the Parameter Entity. I assume that Parameter class contains the getEmployees method.)
#ManyToMany(mappedBy="parameters")

Objectify and relations

I'm trying to implement a simple GAE service. Particularry I have the Student entity and Category entity. To each Student can be associated one or more Categories. How can I create this relationship using Objectify? THanks
Edit: This is my code. Is It valid?
#Entity
public class Studente {
static long nextID = 17;
public static Key<Studente> key(long id) {
return Key.create(Studente.class, id);
}
List<Key<Categoria>> categorie;
public Studente() {}
#Id Long id;
#Index String nome;
#Index String cognome;
#Index String username;
#Index String password;
public Studente(String nome, String cognome, String username, String password) {
this.nome=nome;
this.cognome=cognome;
this.username=username;
this.password=password;
categorie = new ArrayList<Key<Categoria>>();
}
public static long getNextID() {
return nextID;
}
public static void setNextID(long nextID) {
Studente.nextID = nextID;
}
public List<Key<Categoria>> getCategorie() {
return categorie;
}
public void setCategorie(List<Key<Categoria>> categorie) {
this.categorie = categorie;
}
public void addCategoria(Key<Categoria> k ){
categorie.add(k);
}
}
Create a muti-valued indexed field in Student that holds all Category IDs (or Keys):
#Entity
public class Category {
#Id
public Long id; // auto-generated Id of the Category
}
#Entity
public class Student {
#Id
public Long id; // auto-generated Id of the Student
#Index
public List<Long> categories; // put Category Ids into this list
}
Indexed fields can be used in query filters, so you will be able to search for students that belong to certain category.
I would suggest having a third entity with indexed references to both entities. That way, you could easily query for every student in a category, or for every category of a student.
#Entity
public class Student { /*...*/ }
#Entity
public class Category { /*...*/ }
#Entity
public class StudentCategory {
#Id
private Long id;
#Index
private Ref<Student> student;
#Index
private Ref<Category> category;
/*...*/
}
We have a similar setup in our GAE applications, and it has served us well.
See documentation of Ref<?>.

Categories

Resources