Many to one Hibernate mapping - java

So, here'e the situation:
Table ComputerInventory with {computerInventoryID (Primary key), TagID(unique), Name etc}
Table reviewStatus with {reviewStatusID(Primary key), computerInventoryID (ForeignKey), status }
I've written the hibernate Entity for ReviewStatus:
public class ReviewStatus implements Serializable {
public enum reviewStatus {
To_Be_Reviewed,
Reviewed
}
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long reviewStatusId;
#Column(name = "lastModifiedTime")
private Date lastModifiedTime;
#Column(name = "Comments")
private String comments;
#Enumerated(EnumType.STRING)
#Column(name = "status")
//all above params have gettetrs and setters
private reviewStatus status;
//TODO: Mapping to computerinventory
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "computerInventoryId")
//For one ComputerInventoryID, there can be many review Statuses.
private Set<ReviewStatus> reviewStatusSet;
public long getreviewStatusId() {
return reviewStatusId;
}
My Doubts:
For one ComputerInventoryID, there can be many review Statuses, so do I have a
Set<ReviewStatus> reviewStatusSet
where I return the list of entries in reviewstatus? Sorry, but I don't understand how I can write a get/set for returning and setting the reviews status of a bunch of records.

Your reference from ReviewStatus should be to a ComputerInventory, not to its ID. Hibernate lets you abstract out the details of the primary key (the ID), letting you directly reference from one object to another. You should use an #ManyToOne annotation on your private ComputerInventory computerInventory;.

Related

Hibernate: How to update parent_id field without retrieving parent from db?

In a Spring Boot app, I have the following entities that have one-to-many relationship (Category is the parent of Recipe):
#Entity
public class Recipe {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(nullable = false, length = 50)
private String title;
#ManyToOne(optional = true, fetch = FetchType.LAZY)
#JoinColumn(name = "category_id", referencedColumnName = "id")
private Category category;
}
#Entity
public class Category {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(unique = true, nullable = false, length = 50)
private String name;
#OneToMany(mappedBy = "category", cascade = CascadeType.ALL)
private Set<Recipe> recipes = new HashSet<>();
public void addRecipe(Recipe recipe) {
recipes.add(recipe);
recipe.setCategory(this);
}
public void removeRecipe(Recipe recipe) {
recipes.remove(recipe);
recipe.setCategory(null);
}
}
When I create a Recipe, I send categoryId that is selected from Dropdown list and create Recipe by retrieving and adding category to the recipe as shown below:
#Transactional
public void update(RecipeRequest request) {
final Category category = categoryRepository.findById(request.getCategoryId())
.orElseThrow(() -> new NoSuchElementFoundException(NOT_FOUND_CATEGORY));
/* instead of retrieving category, I want to set the categoryId field of Recipe,
but there is not such kind of setter */
recipe.setCategoryId(request.getCategoryId());
recipe.setTitle(capitalizeFully(request.getTitle()));
recipe.setCategory(category);
recipeRepository.save(recipe);
}
Instead of retrieving category, I want to set the categoryId field of Recipe, but there is not such kind of setter:
recipe.setCategoryId(request.getCategoryId());
So, what is the most proper way for just setting the categoryId of the recipe and then saving it without requiring the category from db? Do I need a setter for categoryId field to the Recipe (I thought it, but does not seem elegant way)?
I would just add a categoryId field along with the corresponding getter and settter methods to the Recipe class.
#Entity
public class Recipe {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(nullable = false, length = 50)
private String title;
#ManyToOne(optional = true, fetch = FetchType.LAZY)
#JoinColumn(name = "category_id", referencedColumnName = "id")
private Category category;
#Column(name = "category_id", nullable = false)
private Integer categoryId;
// getters/setters
}
Having a categoryId field means that when we don't have to create an instance of Category when adding new Recipes. Sure, Recipe.category will be null but that's ok if we're just adding new Recipes. This approach could also prove beneficial if we later decide that we need to add many Recipes simultaneously.
If your repository implements org.springframework.data.jpa.repository.JpaRepository you may take advantage of using JpaRepository#getReferenceById method, in that case Hibernate instead of querying DB for data will return proxy object. However, such implementation may cause issues in some cases, for example:
// this call typically returns entity
// or null if entity wasn't found
repository.findById(id);
but:
// this call returns proxy object
repository.getReferenceById(id);
// now instead of returning entity
// repository either returns initialized proxy
// object or throws EntityNotFoundException
// if entity wasn't found
repository.findById(id);

Mapping entities in JPA/Hibernate without adding additional fields (just by using ids)

I'm trying to map those three entities to each other without adding any additional fields to any of them. They should only contain the fields that already exist. I'm also trying to only get columns in the tables that represent the currently existing entity fields- and no additional columns.
#Entity
public class Order {
#Id
private Integer orderId;
private String title;
private Customer customer;
private List<Comment> comments;
}
#Entity
public class Customer {
#Id
private Integer customerId;
private String name;
}
#Entity
public class Comment {
#Id
private Integer commentId;
private Integer orderId;
private String details;
}
My understanding is that I can't simply use #OneToOne, #OneToMany and #ManyToOne mappings, because neither Customer nor Comment has a reference to Order . I'm trying to somehow reference the ids of Customer and Comment directly from Order.
I've tried using #MapsId and #JoinColumn but either I don't know how to properly use them, or they don't do what I think they do.
Is this task at all possible? If so, how to map them to each other?
For the reference to Comment you must use #JoinColum
The Customer reference assumes that there is a customer_id on the order table.
#Entity
public class Order {
#Id
private Integer orderId;
private String title;
#ManyToOne
#JoinColumn(name = "customer_id")
private Customer customer;
#OneToMany
#JoinColumn(name = "comment_id")
private List<Comment> comments;
}

spring-data-jpa 3-way-intersection table

I'll try to illustrate what I'm trying to achieve shortly...
Let's suppose I have a users table:
USER_INFO
USER_ID [PK]
USER_NAME
PASSWORD
an intersection table to define connections for each user (N:M - ManyToMany)
CONNECTION_INFO
CONNECTION_ID [PK]
USER_A_ID [FK - references USER_INFO(USER_ID)]
USER_B_ID [FK - references USER_INFO(USER_ID)]
CONNECTION_TYPE_ID [FK - references CONNECTION_TYPE(CONNECTION_TYPE_ID)]
The CONNECTION_TYPE is simple as:
CONNECTION_TYPE
CONNECTION_TYPE_ID [PK]
CONNECTION_TYPE_NAME [CHECK allowed values are: FRIEND, FAMILY, ...]
On Spring side I defined my User entity as:
#Entity
#Table(name = "USER_INFO")
public class User implements Serializable {
#Id
#NotNull
#Column(name = "USER_ID")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer userId;
#Column(name = "USER_NAME)
private String userName;
#Column(name = "PASSWORD)
private char[] password;
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "CONNECTION_INFO",
joinColumns = { #JoinColumn(name = "USER_A_ID") },
inverseJoinColumns = { #JoinColumn(name = "USER_B_ID") })
private List<User> connections;
// ctor, getters, setters, toString, ...
}
I have a UserRepository interface that extends JpaRepository etc etc. Now, this works perfectly and I can retrieve all connections be it FRIEND, FAMILY, MOST_HATED_PERSONS, BLOCKED, DEMON, etc...
I tried to integrate the ConnectionType too in the picture however...
#Entity
#Table(name = "CONNECTION_TYPE")
public class Connection implements Serializable {
public static enum Types {
FRIEND, FAMILY, BLOCKED, ...
}
#Id
#NotNull
#Column(name = "CONNECTION_TYPE_ID")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer connectionTypeId;
#Column(name = "CONNECTION_TYPE_NAME")
private ConnectionType connectionType;
// ctor, getters, setter, etc
}
Now, my question is, how can I get only specific connections for a given user, based on Connection.Types? For example I want to find only FRIENDs, or only FAMILY I think you get my point. This 3 way intersection table gives me one of a headache.
#Clarification:
What I want is a #ManyToMany relation defined on my User entity that happen to have extra column. I know in that case there are proposed solutions like LINK. In my case this extra column is a foreign key to a third table (USER_INFO(Holds the users), CONNECTION_INFO(Holds the connections between users N:M + an info on the type of connection), CONNECTION_TYPE. If I can model it with spring-data-jpa from what I understand I only need a magic named method under UserRepository, something like (totally incorrect):
public interface UserRepository extends JpaRepository<User, Integer> {
List<User> findUserFriendsByConnectionType(User userWhoseFriendsWeAreSearching, String connectionTypeFromTheThirdTable);
}
That's all I want. I know it's simple with a normal extra column by creating an entity for the intersection table too and break the ManyToMany to OneToMany and ManyToOne, it just happens I have a third table and a possibly ManyToOne (1 connection can have 1 associated type, while a type can be linked to any number of connections) on the intersection entity with the connection_type table.
I hope it clears everything up. The above are just a sample I never imagined we'd hang up on an enum because I wanted to make it look simple I possibly made it way too simple perhaps :).
I managed to solve the problem but I'm not sure if this is the right way to do it. Anyway here's my solution. Consider the following 3 tables:
create table USER_INFO (
USER_ID int not null primary key,
USER_NAME varchar(16),
PASSWORD varchar(64)
);
create table CONNECTION_TYPE (
CONNECTION_TYPE_ID int not null primary key,
CONNECTION_TYPE_NAME varchar(16) not null,
CONNECTION_TYPE_DESCRIPTION varchar(128),
unique (CONNECTION_TYPE_NAME)
);
create table CONNECTION (
CONNECTION_ID int not null primary key,
CONNECTION_TYPE_ID int,
RELATED_USER_ID int,
RELATING_USER_ID int,
foreign key (CONNECTION_TYPE_ID) references CONNECTION_TYPE(CONNECTION_TYPE_ID),
foreign key (RELATED_USER_ID) references USER_INFO(USER_ID),
foreign key (RELATING_USER_ID) references USER_INFO(USER_ID)
With the above 3 tables, I want to provide a functionality to get connections for any given user based on the connection's type. For this I created 3 entities as follows:
#Entity
#Table(name = "CONNECTION_TYPE")
public class ConnectionType implements Serializable {
#Id
#NotNull
#Column(name = "CONNECTION_TYPE_ID")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer connectionTypeId;
#NotNull
#Column(name = "CONNECTION_TYPE_NAME", unique = true)
private String connectionTypeName;
#Column(name = "CONNECTION_TYPE_DESCRIPTION")
private String connectionTypeDescription;
...
}
Nothing particularly interesting in here, I omitted the constructor, getters, setters etc and from the ConnectionType I don't want to have a mapping for all connections for this type so that direction is not present.
#Entity
#Table(name = "CONNECTION")
public class Connection implements Serializable {
#Id
#NotNull
#Column(name = "CONNECTION_ID")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer connectionId;
#NotNull
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "CONNECTION_TYPE_ID", referencedColumnName = "CONNECTION_TYPE_ID")
private ConnectionType connectionType;
#NotNull
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "RELATED_USER_ID", referencedColumnName = "USER_ID")
private User relatedUser;
#NotNull
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "RELATING_USER_ID", referencedColumnName = "USER_ID")
private User relatingUser;
...
}
This one is more interesting if for noone else at least for me. This would be my intersection table entity. There's the uni-directional mapping for the used ConnectionType with ManyToOne as one Connection can have exactly one ConnectionType while the same ConnectionType can be reused for an arbitrary number of Connections.
The other 2 User mappings I'm sure I've messed up but before that here's the User entity:
#Entity
#Table(name = "USER_INFO")
public class User implements Serializable {
#Id
#NotNull
#Column(name = "USER_ID")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer userId;
#NotNull
#Column(name = "USER_NAME")
private String userName;
#NotNull
#Column(name = "PASSWORD")
private char[] password;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "relatedUser", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Connection> connections;
}
Now here I'm even more sure I completely messed up, but I'll show the actual error. My repository is simple as a brick:
#Repository
public interface UserRepository extends JpaRepository<User, Integer> {
}
And I have a UserService with a simplified findAllConnectionsForUserById function:
#Service
public interface UserService {
List<User> findAllConnectionsForUserById(Integer userId);
}
The method implementation is simple enough:
#Override
#Transactional
public List<User> findAllConnectionsForUserById(Integer userId) {
Optional<User> _user = userRepository.findById(userId);
// omitted exception handling...
User user = _user.get();
List<Connection> connections = user.getConnections();
return connections.strea.map(Connection::getRelatingUser).collect(Collectors.toList());
This way it seem to work fine for the simple case and in case I take the ConnectionType too:
connections.stream().filter(c -> c.getConnectionType().getConnectionTypeName().equals("FRIEND")).map(Connection::getRelatingUser).collect(Collectors.toList());
it seem to work as well. Again, not sure if this is the right way but at least it does the job.

JPA: How to handle versioned entities?

I have a versioning on an entity as part of its primary key. The versioning is done via a timestamp of the last modification:
#Entity
#Table(name = "USERS")
#IdClass(CompositeKey.class)
public class User {
#Column(nullable = false)
private String name;
#Id
#Column(name = "ID", nullable = false)
private UUID id;
#Id
#Column(name = "LAST_MODIFIED", nullable = false)
private LocalDateTime lastModified;
// Constructors, Getters, Setters, ...
}
/**
* This class is needed for using the composite key.
*/
public class CompositeKey {
private UUID id;
private LocalDateTime lastModified;
}
The UUID is translated automatically into a String for the database and back for the model. The same goes for the LocalDateTime. It gets automatically translated to a Timestamp and back.
A key requirement of my application is: The data may never update or be deleted, therefore any update will result in a new entry with a younger lastModified. This requirement is satisfied with the above code and works fine until this point.
Now comes the problematic part: I want another object to reference on a User. Due to versioning, that would include the lastModified field, because it is part of the primary key. This yields a problem, because the reference might obsolete pretty fast.
A way to go might be depending on the id of the User. But if I try this, JPA tells me, that I like to access a field, which is not an Entity:
#Entity
#Table(name = "USER_DETAILS")
public class UserDetail {
#Id
#Column(nullable = false)
private UUID id;
#OneToOne(optional = false)
#JoinColumn(name = "USER_ID", referencedColumnName = "ID")
private UUID userId;
#Column(nullable = false)
private boolean married;
// Constructors, Getter, Setter, ...
}
What would be the proper way of solving my dilemma?
Edit
I got a suggestion by JimmyB which I tried and failed too. I added the failing code here:
#Entity
#Table(name = "USER_DETAILS")
public class UserDetail {
#Id
#Column(nullable = false)
private UUID id;
#OneToMany
#JoinColumn(name = "USER_ID", referencedColumnName = "ID")
private List<User> users;
#Column(nullable = false)
private boolean married;
public User getUser() {
return users.stream().reduce((a, b) -> {
if (a.getLastModified().isAfter(b.getLastModified())) {
return a;
}
return b;
}).orElseThrow(() -> new IllegalStateException("User detail is detached from a User."));
}
// Constructors, Getter, Setter, ...
}
What you seem to require seems to be on the lines of a history table, to keep track of the changes. See https://wiki.eclipse.org/EclipseLink/Examples/JPA/History on how EclipseLink can handle this for you while using normal/traditional JPA mappings and usage.
What you have here is a logical 1:1 relationship which, due to versioning, becomes a technical 1:n relationship.
You have basically three options:
Clean JPA way: Declare an 'inverse' #ManyToOne relationship from user to the "other object" and make sure you always handle it whenever a new User record is created.
'Hack-ish' way: Declare a #OneToMany relationship in the "other object" and force it to use a specific set of columns for the join using #JoinColumn. The problem with this is that JPA always expects unique reference over the join columns so that reading the UserDetail plus referenced User records should work, whereas writing UserDetail should not cascade onto User to avoid unwanted/undocumented effects.
Just store the user's UUID in the "other object" and resolve the reference yourself whenever you need it.
The added code in your question is wrong:
#JoinColumn(name = "USER_ID", referencedColumnName = "ID")
private UUID userId;
More correct, albeit not with the result you want, would be
#JoinColumn(name = "USER_ID", referencedColumnName = "ID")
private User user;
This won't work though, because, as I said above, you may have more than one user record per UserDetail, so you'd need a #OneToMany relationship here, represented by a Collection<User>.
Another 'clean' solution is to introduce an artificial entity with a 1:1 cardinality w.r.t. to the logical User to which you can refer, like
#Entity
public class UserId {
#Id
private UUID id;
#OneToMany(mappedBy="userId")
private List<User> users;
#OneToOne(mappedBy="userId")
private UserDetail detail;
}
#Entity
public class User {
#Id
private Long _id;
#ManyToOne
private UserId userId;
}
#Entity
public class UserDetail {
#OneToOne
private UserId userId;
}
This way, you can somewhat easily navigate from users to details and back.
I came to a solution, that is not really satisfying, but works. I created a UUID field userId, which is not bound to an Entity and made sure, it is set only in the constructor.
#Entity
#Table(name = "USER_DETAILS")
public class UserDetail {
#Id
#Column(nullable = false)
private UUID id;
#Column(nullable = false)
// no setter for this field
private UUID userId;
#Column(nullable = false)
private boolean married;
public UserDetail(User user, boolean isMarried) {
this.id = UUID.randomUUID();
this.userId = user.getId();
this.married = isMarried;
}
// Constructors, Getters, Setters, ...
}
I dislike the fact, that I cannot rely on the database, to synchronize the userId, but as long as I stick to the no setter policy, it should work pretty well.

Persisting a #ManyToOne-referenced object only if it does not exist

I'm fairly new to Spring/JPA so this is somewhat a trivial question.
I have two entities with a many-to-one relationship: Item and ItemType. Basically, ItemType simply represents a unique name for a set of Items. I use a CrudRepository<Item, Long> to store them. The relevant code is as follows (getters/setters/equals()/hashCode() omitted):
#Entity
public class Item {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#JoinColumn(name = "type_id")
private ItemType itemType;
public Item() {}
public Item(ItemType itemType) {
this.itemType = itemType;
}
}
#Entity
public class ItemType {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#Column(unique = true, nullable = false)
private String name;
public ItemType() {}
public ItemType(String name) {
this.name = name;
}
}
#Controller
public class ItemsController {
#Autowired private ItemsRepo itemsRepo;
#RequestMapping(value = "/item", method = RequestMethod.POST)
#ResponseBody
public Item addQuestionSet(#RequestBody Item item) {
return itemsRepo.save(item);
}
}
When I insert a new Item into the database, I want it to get a type_id from either an ItemType with the given name if it already exists, or from a newly persisted ItemType otherwise.
As of now, I naturally get an exception when trying to insert the second item with the same type:
org.hsqldb.HsqlException: integrity constraint violation: unique constraint or index violation
I could probably make a boilerplate check in my controller before saving a new item into repository. But this task is rather generic, I'm pretty sure there must be a convenient solution in JPA.
Thanks.
It seems you are persist() method on the Item object rather than merge() method. I hope it will resolve your query.
I can see that the problem is when you "persist", try with "lazy" type. You could get the data only when you need it and EAGER always.
I can give you an example how i do it
this is my class "CentroEstudio"
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "idCentroEstudio",nullable=false)
private Long idCentroEstudio;
#ManyToOne(fetch = FetchType.EAGER,cascade=CascadeType.ALL)
#JoinColumn(name = "idTipoCentroEstudio", nullable = false)
private TipoCentroEstudio tipoCentroEstudio;
#Column(name="nombre",nullable=false)
private String nombre;
#Column(name="activo",nullable=false)
private boolean activo;
this is my class "TipoCentroEstudio"
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name="idTipoCentroEstudio",nullable=false)
private Long idTipoCentroEstudio;
#Column(name="descripcion",nullable=false)
private String descripcion;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "tipoCentroEstudio")
private Set<CentroEstudio> centroEstudio = new HashSet<CentroEstudio>(0);
I'm sorry for the Spanish in the example, but I'm peruvian and I speak Spanish.
I hope this helps you ...

Categories