How are association tables/foreign keys best handled with hibernate? - sql

I used schemaExport to create my tables like this
Configuration config = new Configuration();
config.addAnnotatedClass(Categories.class);
config.addAnnotatedClass(Article.class);
config.configure();
new SchemaExport(config).create(true, true);
The important pieces of Categories.java and Article.java are these:
Article.java
#Id
#GeneratedValue
public Long getId() {
return id;
}
#OneToMany( cascade = CascadeType.ALL )
public Set<Categories> getCategories() {
return categories;
}
Categories.java
#Id
#GeneratedValue
public Long getId() {
return id;
}
#ManyToOne(cascade = CascadeType.ALL)
public Article getArticleCategories() {
return articleCategories;
}
When run, Schema Export generates three tables: Article, Categories and article_categories. article_categories is an association table with two primary keys matching the keys from Article and Categories. Only Article and Categories have been added to the SessionFactory in my ApplicationContext.xml because I obviously don't have the third table as a class since it was generated. I wrote this HQL:
"from Article a, article_categories ac, Categories c where ac.Article_id = a.id and ac.categories_id = c.id and c.category = 'p'"
Hibernate can't run it because article_categories is not mapped. Do I need to map it? Is running native SQL instead of HQL a possible alternative, or should I avoid association tables all together? What's the solution to this issue?

HQL doesn't work with tables. It works with entities and their associations. Create joins between the entities in the HQL query, and Hibernate will generate the appropriate SQL, using the join tables, for you.
Example:
select a from Article a
inner join a.categories c
where c.category = :categoryName
Read (at least) the HQL section of the reference manual.
Side note: the name of the entities should be singular: Category and not Categories. An article thus has a Set<Category>, named categories. Much easier to read and understand that way. And the category field of Category should be named name: it's the name of the category, and not its category.

Not too sure, if you have an error in your original database or the export. We use the same approach but refactor the tables afterwards. (I'll try to find the script we use, if you need it and get back to this tomorrow)
In any case: You don't need an entity class for your m:n relationship, if there ar no additional fields other than the foreign keys:
In Category:
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinTable(name = "article_categories",
joinColumns = { #JoinColumn(name = "article_id", nullable = false, updatable = false) },
inverseJoinColumns = { #JoinColumn(name = "category_id", nullable = false, updatable = false) })
private Set<Article> articles;
In Article:
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinTable(name = "article_categories",
joinColumns = { #JoinColumn(name = "category_id", nullable = false, updatable = false) },
inverseJoinColumns = { #JoinColumn(name = "article_id", nullable = false, updatable = false) })
private Set<Category> categories;
This will create the m:n table.
When we run the hibernate exporter, we actually get a mapping class and a key class, which you'll need if you want additional columns in the m:n table. But like I said, I'll have to dig up the snippet tomorrow.

Related

How to access the join column (Foreign key) in Spring Data JPA without loading the mapped entity

I have a POST entiry and HeadingSlugEntity.
public class PostEntity implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String postId;
private String uid;
private String dateCreated;
private String dateUpdated;
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JoinColumn(name = "headingSlugId", referencedColumnName = "headingSlugId")
private HeadingSlugEntity headingSlugEntity;
}
I have a 1:1 mapping for HeadingSlugEntity. When I save post entity and headingSlugEntity together, headingSlugId is getting auto-populated. That is working fine.
The next time when I fetch the post entity, I need to access the headingSlugId without getting the headingSlugEntity. (At this point of time, I don't need HeadingSlugEntity, I just need its ID.) I just need to get the headingSlugId. How to do it.
Since this entity is not having the field headingSlugId, I can't set and get it. If I add a field named headingSlugId inside post entity, I will get hibernate duplicate field error. If I put
insertable = false, updatable = false)
and add field headingSlugId, the headingSlugId will be Null.
What basic stuff I am missing here ?
You can map the foreign key to the entity twice in this case, one for actual ORM and another for getting the FK without actually firing a new query.
public class Answer {
#Column(name = "headingSlugId", insertable = false, updatable = false)
private Integer headingSlugId;
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JoinColumn(name = "headingSlugId", referencedColumnName = "headingSlugId")
private HeadingSlugEntity headingSlugEntity;
}

How to include additional data in one entity from other tables (via joins or subqueries) and still have it be insertable/updatable?

So I have the following code working correctly on my ecommerce site.
#Entity
#Table(name = "v_customer_wishlist")
#NamedQuery(name = "VCustomerWishlist.findAll", query = "SELECT w FROM VCustomerWishlist w")
public class VCustomerWishlist implements Serializable {
#Id
#Column(name = "cart_id")
private int _cartId;
//get/set methods
...
#OneToMany(mappedBy = "_wishlist", cascade = CascadeType.ALL)
private List<VCustomerWishlistItem> _items;
//get/set methods
}
#Entity
#Table(name = "v_customer_wishlist_items")
#NamedQuery(name = "VCustomerWishlistItem.findAll", query = "SELECT i FROM VCustomerWishlistItem i")
public class VCustomerWishlistItem implements Serializable {
...
public VCustomerWishlistItem(int cartId, int productId) {
VCustomerWishlistItemPK id = new VCustomerWishlistItemPK (cartId, productId);
setId(id);
}
#EmbeddedId
private VCustomerWishlistItemPK id; //is PK for cartId and productId
//get/set methods
...
#ManyToOne
#JoinColumn(name = "cart_id")
private VCustomerWishlist _wishlist;
//get/set methods
...
#Column(name = "product_name")
private String _productName;
//get/set methods
...
}
Then somewhere in my backing bean, I could do somethin like this (simplified version):
...
VCustomerWishlist wishlist = getCustomer().getWishlistById(cartId);
...
VCustomerWishlistItem item = new VCustomerWishlistItem(wishlist.getId(), product.getId());
...
item.setSequenceNum(wishlist.getItems().size()+1);
item.setProductName(product.getName());
item.setQuantity(1);
wishlist.addItem(item);
wishlistItemService.save(item);
...
So I can add items (product references) to wishlist and JPA will correctly generate the INSERT INTO queries and so forth.
However, upon thinking about it, I thought it would be better to retrieve this data directly from my 'master_products' table instead of what was stored in the VCustomerWishlistItem.
This way I would always have the most up-to-date productName, unitPrice and so forth for wishlist items saved weeks or months before.
The thing is, if I modify the view in my database to include this additional info by adding joins or subqueries; as soon as add joins or subqueries to my view, it becomes non-inserable/updatable.
I thought that it could be done via JPLQ in one #NamedQuery definition, but I understand these are designed to be used manually when retrieving desired sets. As opposed to the nice built-in way that JPA automatically retrieves the wishlist.items resolving it with the indicating annotation properties.
The fantasy property would be one where I can specify a direct table source for the entity, ignoring the join and subquery tables.
So for example, if the source for 'v_customer_wishlist_items_readonly' was:
SELECT
`cwi`.`cart_id` AS `cart_id`,
`cwi`.`product_id` AS `product_id`,
`cwi`.`sequence_num` AS `sequence_num`,
`mp_readonly`.`product_name` AS `product_name`,
`mp_readonly`.`product_web_id` AS `product_web_id`,
`mp_readonly`.`unit_price` AS `unit_price`,
`cwi`.`quantity` AS `quantity`,
`mp_readonly`.`unit_price`*`csci`.`quantity` AS `item_subtotal`,
`cwi`.`create_datetime` AS `create_datetime`,
`cwi`.`update_datetime` AS `update_datetime`
FROM
`customer_wishlist_items` `cwi` JOIN `master_products` `mp_readonly` ON `cwi`.`product_id` = `mp_readonly`.`product_id`
ORDER BY `cwi`.`sequence_num`;
It would be ideal to have a an annotation where I could indicate that primary table name is 'customer_wishlist_items', so all updates/inserts would only apply to this table and changes to the rest of the read-only fields would be ignored.
So somethint like this:
#Entity
#Table(name = "v_customer_wishlist_items_readonly")
#PrimaryTable(name = "customer_wishlist_items") //fantasy annotation
#NamedQuery(name = "VCustomerWishlistItem.findAll", query = "SELECT s FROM VCustomerWishlistItem s")
public class VCustomerWishlistItem implements Serializable {
...
Does anyone know what would be the correct way of implementing this?
Thanks a lot in advance!
Why not use derived ids or the MapsId to let JPA set your foreign key/id columns for you?
#Entity
#Table(name = "v_customer_wishlist_items")
#NamedQuery(name = "VCustomerWishlistItem.findAll", query = "SELECT i FROM VCustomerWishlistItem i")
public class VCustomerWishlistItem implements Serializable {
...
public VCustomerWishlistItem(VCustomerWishlist cart, Product product) {
this._wishList = cart;
this._product = product;
setId(new VCustomerWishlistItemPK());//JPA will populate this for you
}
#EmbeddedId
private VCustomerWishlistItemPK id; //is PK for cartId and productId
//get/set methods
...
#ManyToOne
#MapsId("cartId")
#JoinColumn(name = "cart_id")
private VCustomerWishlist _wishlist;
//get/set methods
...
#MapsId("productId")
#JoinColumn(name = "product_id")
private Product _product;
//get/set methods
...
}
With this, you don't need to have or lookup the cartId/productId values at all as JPA will figure them out and set them for you, allowing you do just use code like:
VCustomerWishlistItem item = new VCustomerWishlistItem(wishlist, product);
...
item.setSequenceNum(wishlist.getItems().size()+1);
item.setQuantity(1);
wishlist.addItem(item);
wishlistItemService.save(item);
You should probably just set the sequenceNum and add the item to the wishlist in the item constructor, though I'm not a fan this approach to sequencing as it can lead to concurrency issues and problems maintaining it.
You can also do away with the EmbeddedId if you don't need it within your entity and use it as a primary key class; you'd just have to change the property names within it to match the relationships names from the entity:
#Entity
#IdClass(VCustomerWishlistItemPK.class)
#Table(name = "v_customer_wishlist_items")
#NamedQuery(name = "VCustomerWishlistItem.findAll", query = "SELECT i FROM VCustomerWishlistItem i")
public class VCustomerWishlistItem implements Serializable {
...
public VCustomerWishlistItem(VCustomerWishlist cart, Product product) {
this._wishList = cart;
this._product = product;
}
...
#ManyToOne
#Id
#JoinColumn(name = "cart_id")
private VCustomerWishlist _wishlist;
//get/set methods
...
#Id
#JoinColumn(name = "product_id")
private Product _product;
//get/set methods
...
}
The primary key class might then look like:
public class VCustomerWishlistItemPK {
public Integer _product;
public Integer _wishlist;
//optional getter/setter methods..
}
The properties within the ID class must match the names of the properties in your entities, but use the type of the primary key from the referenced class.

(N + 1) selects problem with child Map-collection

friends! I have these entities:
Document:
#Entity
#Table(name = "documents")
public class Document extends AbstractNamedEntity {
....
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "document_change_notices",
joinColumns = #JoinColumn(name = "document_id"),
inverseJoinColumns = #JoinColumn(name = "change_notice_id"))
#MapKeyColumn(name = "change")
private Map<Integer, ChangeNotice> changeNotices;
....
}
and ChangeNotice:
#Entity
#Table(name="change_notices")
public class ChangeNotice extends AbstractNamedEntity {
....
#ElementCollection(fetch = FetchType.LAZY)
#CollectionTable(name = "document_change_notices", joinColumns = #JoinColumn(name = "change_notice_id"))
#MapKeyJoinColumn(name = "document_id")
#Column(name = "change")
private Map<Document, Integer> documents;
....
}
And these are my repositories:
For Document:
public interface DocumentRepository extends JpaRepository<Document, Integer> {
....
#Query("select d from Document d left join fetch d.changeNotices where d.decimalNumber=:decimalNumber")
Optional<Document> findByDecimalNumberWithChangeNotices(#Param("decimalNumber") String decimalNumber);
....
}
and for ChangeNotice:
public interface ChangeNoticeRepository extends JpaRepository<ChangeNotice, Integer> {
....
#Query("select c from ChangeNotice c left join fetch c.documents where c.id=:id")
Optional<ChangeNotice> findByIdWithDocuments(#Param("id") int id);
....
}
So, when i want to get Document with changeNotices, thats not a problem, i have only one select.
But when i want to get ChangeNotice with documents - i have (n + 1), first for document, and n for changeNotices Map.
I use join fetch in my query, but it does not help.
I think the problem is that in Document i have Map<K,V>, where entity is a value, and i should use #ManyToMany relationship. And in ChangeNotice i have Map<K,V>, where entity is a key, and i should use #ElementCollection.
Are there any ways to write a query that select ChangeNotice with documents in one select? (without changing my entities code, may be small fixes possible)
So, a lot of time has passed, i didn't find an answer. But it is my architecture issue. I had to use another class, that contains Document, ChangeNotice, and Integer field. My Document and ChangeNotice entities had child collection of this class with #OnetoMany relationship. It solves the problem.

Filter elements of fetched collection. EclipseLInk. Criteria API

I have an object:
#Entity
#Table(name = "users", catalog = "adm", uniqueConstraints = #UniqueConstraint(columnNames = "loginUser"))
public class User implements GenericEntityPK<Integer> {
private Set<Permission> permissions = new HashSet<Permission>(0);
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "user_l_permission", catalog = "adm", joinColumns = {
#JoinColumn(name = "idUser", nullable = false, updatable = false) }, inverseJoinColumns = {
#JoinColumn(name = "idPermission", nullable = false, updatable = false) })
public Set<Permission> getPermissions() {
return this.permissions;
}
}
I want to select user entity and fetch collection of permissions, with where clause. In other words permissions collection must contain only elements that satisfy where conditions.
I've tried to do it with SetJoin but with no success..
Is there a way to do it?
I would recommend you instead query for the permissions that match the user and filter you want when needed. You can even cache this collection in your entity with a transient collection to pass it around in the object. Using filters on relationships ends up causing problems and corrupts the cache as what is in the object is no longer a true representation of what is in the database.
That said, there is no JPA way to do it, but Eclipselink supports additional criteria

JPA #ElementCollection generates strange unique key

I have an entity class PositionOrdering which contains an element collection:
#ElementCollection(targetClass = Position.class, fetch = FetchType.EAGER)
#CollectionTable(name = "POSITION_ORDERING_POSITION",
joinColumns = #JoinColumn(name = "position_ordering_id"))
#OrderColumn
List<Position> positions = new ArrayList<>();
When hibernate generates the database structure, it looks like this:
CREATE TABLE wls.position_ordering_position
(
position_ordering_id bigint NOT NULL,
positions_id bigint NOT NULL,
positions_order integer NOT NULL,
...
}
It's ok and exactly what I was expected. But it also generate a unique contsraint on positions_id column. It is strange, because the position id should be unique only per ordering, so any of the following unique keys would be ok:
position_ordering_id + positions_order
position_ordering_id + positions_id
But not on the single column of positions_id.
Because the constraint is generated automatically, I can't ignore or remove it simply.
Can I configure my collection to create correct unique constraint or at least not to create any?
UPDATE:
As for request, here is the skeleton of the Position entity:
#Entity
#SequenceGenerator(name = EntityBase.SEQUENCE_NAME,
sequenceName = "POSITION_ID_SEQ")
#Table(name = "position")
public class Position extends EntityBase {
// Lots of fields, like row, column number, and type, etc.
}
Where EntityBase is a simple class with some utility function and with Id:
#MappedSuperclass
public abstract class EntityBase implements Serializable, Cloneable {
public static final String SEQUENCE_NAME = "SEQUENCE_GENERATOR";
#Id
#GeneratedValue(strategy = GenerationType.AUTO, generator = SEQUENCE_NAME)
protected Long id;
//..
}
#ElementCollection is used for mapping basic types or #Embedded classes, not entities. From the documentation
An ElementCollection can be used to define a one-to-many relationship to an Embeddable object, or a Basic value (such as a collection of Strings).
Since Position is an #Entity, you should map it as #OneToMany or #ManyToMany. I don't know the exact reason why are you getting that unique key generated, but I guess you can expect unpredictable results if you use the annottion in a was that it was not intended for.
As Predrag Maric described it in the accepted answer, the problem was that Position was not an `Embeddable'. My solution was:
I created a support class which wraps the Position into an #Embeddable entity:
#Embeddable
//#Table(name = "position_ordering_position")
public class PositionOrderingPosition {
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "position_id", nullable = false)
private Position position;
public PositionOrderingPosition() {
}
public PositionOrderingPosition(Position position) {
this.position = position;
}
public Position getPosition() {
return position;
}
}
Also I changed the Element collection to this:
#ElementCollection(fetch = FetchType.EAGER)
#CollectionTable(name = "POSITION_ORDERING_POSITION",
joinColumns = #JoinColumn(name = "position_ordering_id"))
#OrderColumn
List<PositionOrderingPosition> positions = new ArrayList<>();
Now it creates the same table, but with the right constraints.