Using Map in Hibernate is not working - java

I am trying to create a simple program by using java.util.Map. I have created a Customer entity class with a map of Order classes. Here are my Java classes:
Customer.java
#Entity
public class Customer {
#Id
#GeneratedValue
private Integer id;
#OneToMany(mappedBy = "customer")
#MapKey(name = "orderNumber")
private Map<String, Order> orders;
}
Order.java
#Entity
#Table(name="TB_ORDER")
public class Order {
#Id
#GeneratedValue
private Integer id;
private String orderNumber;
#ManyToOne
private Customer customer;
}
Here is my program that tries to save a customer with some orders and then I am trying to display the saved data:
public class AppTest {
public static void main(String[] args) {
Session session = HibernateUtil.getSessionFactory().getCurrentSession();
saveCustomer(session);
session = HibernateUtil.getSessionFactory().getCurrentSession();
showCustomer(session);
HibernateUtil.getSessionFactory().close();
}
private static void showCustomer(Session session) {
session.getTransaction().begin();
List<Customer> list = session.createQuery("from Customer").list();
for (Customer customer : list) {
System.out.println("customer id : "+customer.getId()+ ", customer orders : "+customer.getOrders());
}
session.getTransaction().commit();
}
private static void saveCustomer(Session session) {
session.getTransaction().begin();
Customer customer = new Customer();
Order order = new Order();
order.setCustomer(customer); order.setOrderNumber("100");
Map<String, Order> map = new HashMap();
map.put("100", order);
customer.setOrders(map);
session.save(customer); session.save(order);
session.getTransaction().commit();
}
}
After running the program, Hibernate generated below DDL & DML commands:
Hibernate: create table Customer (id number(10,0) not null, primary key (id))
Hibernate: create table TB_ORDER (id number(10,0) not null, orderNumber varchar2(255 char), customer_id number(10,0), primary key (id))
Hibernate: alter table TB_ORDER add constraint FK_qr7tjivmclv5trf0sbwxki4v foreign key (customer_id) references Customer
Hibernate: insert into Customer (id) values (?)
Hibernate: insert into TB_ORDER (customer_id, orderNumber, id) values (?, ?, ?)
Hibernate: select customer0_.id as id1_0_ from Customer customer0_
customer id : 1, customer orders : null
Hibernate generated only single select query to get the Customer details but not getting any data from TB_ORDER table. So when I try to get the Orders from Customer I am getting null. Please let me know where is the mistake in this code.

It looks like you are missing the #JoinColumn annotation through which you tell who is the owner of the relationship. For this reason i think you should change the class order like this:
#Entity
#Table(name="TB_ORDER")
public class Order {
#Id
#GeneratedValue
private Integer id;
private String orderNumber;
#ManyToOne
#JoinColumn(name = "customer_id")
private Customer customer;
}
And should work fine.

You have to also use #MapKeyJoinColumn as well its better to initialize your Map in pojo and try again .
private Map<String, Order> orders =new Map<String, Order>();

Related

How to select all fields of joined entities using hibernate?

I have two tables:
Limit_utilisation_history with fields:
bigint limit_utilisation_history_id (PK)
numeric(20,2) utilisation_amount
bigint limit_id (FK to limit_utilisation table)
Limit_utilisation with fields:
bigint limit_id (PK)
varchar customer_id
So both tables are related.
I need to expose the result of the following query via rest call:
select limit_utilisation_history_id, utilisation_amount, limit_id, customer_id
where customer_id in (some list of values)
I have done it in the following way:
#Entity
#Data
public class LimitUtilisation{
#Id
private Long limitIdl
private String customerId;
}
#Entity
#Data
#NamedQuery{
name = "LimitUtilisationHistory.getByCustomer",
query = "FROM LimitUtilisationHistory luh FETCH ALL PROPERTIES " +
"INNER JOIN luh.limitId al " +
"WHERE al.customerId in :values"
}
public class LimitUtilisationHistory{
#Id
private Long limitUtilisationHistoryId;
#OneToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "limit_id", referencedColumnName = "limitId")
private LimitUtilisation limitId;
}
public interface LimitUtilisationHistoryRepository extends PagingAndSortingRepository<LimitUtilisationHistory, Long> {
#RestResource(path = "byCustomerId")
#Query
List<LimitUtilisationHistory> getByCustomer (#Param("values") List<String> customer);
}
It works fine, however when I call my rest endpoint I have only utilisation_amount value, others (mainly PK, FK, customer id are missing).
Does anyone have an idea how to do it correctly?
select limit_utilisation_history_id, utilisation_amount, limit_id, customer_id
where customer_id in (some list of values)
Remark: I cannot update DB structure. My intention is to only read from existing DB structure
As you you using FetchType.EAGER I don't see any reason of using FETCH ALL in the query, Could you please try with the below query :
select luh.limit_utilisation_history_id, luh.utilisation_amount, luh.limit_id, lu.customer_id
from LimitUtilisationHistory luh , LimitUtilisation lu
where luh.limit_id = lu.limit_id
and lu.customerId in :values

jpa unidirectional One-to-Many with a Collection of Abstract type

I'm having an issue with JPA. Basically, what I have is an entity with a list of an abstract type and I need each element of the list to be persisted in its corresponding table with a foreign key (to relate to the entity). Here is the code:
#Entity(name = "entity")
public class Entity{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private BigInteger id;
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name="entity_id", referencedColumnName="id")
private List<AbstractType> types;
}
Abstract type:
#Entity
#Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)
public abstract class AbstractType{
#Id
#GeneratedValue(strategy = GenerationType.TABLE)
private BigInteger id;
private String someProp;
#Column(name="entity_id")
private BigInteger entityId;
}
Type A:
#Entity(name = "type_a")
#Transactional
#Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)
public class TypeA extends AbstractType{
private String prop1;
private String prop2;
}
Type B:
#Entity(name = "type_b")
#Transactional
#Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)
public class TypeB extends AbstractType{
private String prop3;
private String prop4;
}
I'm having a SQL error. The generated query tries to update the table of the abstract type (which shouldnt exist). This is part of the query:
update hibernate_sequences set sequence_next_hi_value = ? where
sequence_next_hi_value = ? and sequence_name = 'abstract_type'
insert into type_a (some_prop, entity_id, prop1, prop2) values (?, ?, ?, ?)
insert into type_b (some_prop, entity_id, prop3, prop4) values (?, ?, ?, ?)
update abstract_type set entity_id=? where id=?
As you can see, it's trying to update a table which doesn't (and shouldnt) exist. 'abstract_type' table.
Thanks in advance.
Of course you can't name a class Entity without causing problems.
You can make this much simpler by minimizing the annotations and let JPA do it's work. A Container class holds a collection of AbstractTypes:
#Entity
public class Container {
#Id #GeneratedValue
private Long id;
#OneToMany(cascade=CascadeType.ALL)
private List<AbstractType> types;
public List<AbstractType> getTypes() { return types; }
public void setTypes(List<AbstractType> types) { this.types = types; }
}
The AbstractType is just that:
#Entity
public abstract class AbstractType {
#Id #GeneratedValue
private Long id;
}
And a couple concrete types to extend and inherit from the abstract superclass:
EDIT: A FK back to the Container class can be added with a ManyToOne association.
#Entity
public class TypeA extends AbstractType {
#ManyToOne
private Container container;
}
And:
#Entity
public class TypeB extends AbstractType {
#ManyToOne
private Container container;
}
When I run this Minimal, Complete, Verifiable Example I get the following output:
Hibernate: create table AbstractType (DTYPE varchar(31) not null, id bigint not null, container_id bigint, primary key (id))
Hibernate: create table Container (id bigint not null, primary key (id))
Hibernate: create table Container_AbstractType (Container_id bigint not null, types_id bigint not null)
Hibernate: insert into Container (id) values (?)
Hibernate: insert into AbstractType (container_id, DTYPE, id) values (?, 'TypeA', ?)
Hibernate: insert into AbstractType (container_id, DTYPE, id) values (?, 'TypeB', ?)
Hibernate: insert into Container_AbstractType (Container_id, types_id) values (?, ?)
Hibernate: insert into Container_AbstractType (Container_id, types_id) values (?, ?)
Hibernate: select container0_.id as id1_1_ from Container container0_
model.Container#40591559

How to map two tables to one entity using foreign-key?

I have a problem very similar to this: How do I join tables on non-primary key columns in secondary tables?
But I'm not sure if I can apply the same solution.
I have two tables like these:
CREATE TABLE CUSTOMER
(
CUSTOMER_ID INTEGER NOT NULL,
DETAIL_ID INTEGER NOT NULL,
PRIMARY KEY( CUSTOMER_ID ),
CONSTRAINT cust_fk FOREIGN KEY( DETAIL_ID ) REFERENCES DETAILS( DETAIL_ID )
)
CREATE TABLE DETAILS
(
DETAIL_ID INTEGER NOT NULL,
OTHER INTEGER NOT NULL,
PRIMARY KEY( DETAIL_ID )
)
I'd like to map these tables to a single class called Customer, so I have:
#Entity
#Table(name = "CUSTOMERS")
#SecondaryTable(name = "DETAILS", pkJoinColumns=#PrimaryKeyJoinColumn(name="DETAIL_ID"))
public class Customer {
#Id
#GeneratedValue
#Column(name = "CUSTOMER_ID")
private Integer id;
#Column(table = "DETAILS", name = "OTHER")
private Integer notes;
// ...
}
but this works only if DETAIL_ID matches CUSTOMER_ID in the primary table.
So my question is: how can i use a foreign-key field in my primary table to join on the primary-key of the secondary table?
UPDATE
I tried to set:
#SecondaryTable(name = "DETAILS", pkJoinColumns=#PrimaryKeyJoinColumn(name="DETAIL_ID", referencedColumnName="DETAIL_ID"))
but when I run the application I get this exception:
org.hibernate.MappingException: Unable to find column with logical name: DETAIL_ID in org.hibernate.mapping.Table(CUSTOMERS) and its related supertables and secondary tables
For anyone looking for an answer to this, using #SecondaryTable is not the way to join two tables with non-primary key columns, because Hibernate will try to assosiate the two tables by their primary keys by default; you have to use #OneToMany review http://viralpatel.net/blogs/hibernate-one-to-many-annotation-tutorial/ for a solution, here's a code snippet in case that url stops working:
Customer Class:
#Entity
#Table(name="CUSTOMERS")
public class Customer {
#Id
#GeneratedValue
#Column(name="CUSTOMER_ID")
private Integer id;
#ManyToOne
#JoinColumn(name="DETAIL_ID")
private Details details;
// Getter and Setter methods...
}
Details Class:
#Entity
#Table(name="DETAILS")
public class Details {
#Id
#GeneratedValue
#Column(name="DETAIL_ID")
private int detailId;
#Column(name="OTHER")
private String other;
#OneToMany(mappedBy="details")
private Set<Customer> customers;
// Getter and Setter methods...
}
This is easily accessible through hibernate with the following code:
Session session = HibernateUtil.getSessionFactory().openSession();
Query query = session.createQuery("select id, details.other from Customer");
I hope this helps anyone out there spending hours searching for a way to achieve this like I did.
You can use the referenceColumnName attribute of the #PrimaryKeyJoinColumn annotation to define the join column to the referenced table. In fact, by combining use of name/referencedColumnName you can join on arbitrary on both sides, with the constraint that if duplicates are found your ORM provider will throw an exception.

Hibernate Foreign Key Syntax

I am trying to learn hibernate. I have a movie table with a foreign key to a genre table. Each movie is assigned to a single genre. Each genre may be assigned to many movies.
Here are the table definitions:
CREATE TABLE `movie` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`title` varchar(64) NOT NULL,
`genre_id` int(11) NOT NULL,
PRIMARY KEY (`id`),
KEY `fk_genre` (`genre_id`),
CONSTRAINT `fk_genre` FOREIGN KEY (`genre_id`) REFERENCES `genre` (`id`) ON UPDATE CASCADE,
)
CREATE TABLE IF NOT EXISTS `genre` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(32) DEFAULT NULL,
PRIMARY KEY (`id`)
)
For code I have
#Entity
public class Movie implements java.io.Serializable {
private static final long serialVersionUID = 1;
private Integer id;
private String title;
private Genre genre;
...
#ManyToOne
public Genre getGenre() {
return genre;
}
Also
#Entity
public class Genre implements java.io.Serializable {
private static final long serialVersionUID = 1;
private Integer id;
private String name;
#Id
#GeneratedValue
#Column(name = "id")
public Integer getId() {
return this.id;
}
Then the select generated by hibernate looks like
select
movie0_.id as column1_4_,
movie0_.genre_id as genre11_4_,
movie0_.title as title4_
from
Movie movie0_
And this not right as there's no reference to the genre table. The correct query should have a join with the genre table. More like
select
movie0_.id as column1_4_,
genre.name as genre11_4_,
movie0_.title as title4_
from
Movie movie0_, Genre genre
where
movie0_.genre_id = genre.id;
I'm a little bit of a loss as to what I'm doing wrong. Should the many to one annotation be in the Genre class instead of the Movie class? Or do you see anything else that I'm doing wrong?
Based on the advise below, Movie now has
#Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(id).append(" ");
sb.append(title).append(" ");
this.getGenre(); //new
sb.append(genre.getName());
return sb.toString();
}
#ManyToOne(fetch=FetchType.EAGER) //new
public Genre getGenre() {
return genre;
}
And the way I'm loading Movie is through
public static void main(String[] args) {
SessionFactory sf = HibernateUtil.getSessionFactory();
Session session = sf.openSession();
List<Movie> movies = session.createQuery("from Movie").list();
for (Movie movie : movies) {
System.out.println(movie.toString());
}
session.close();
}
What I'm seeing is that even though I have the eager load and I'm explicitly saying getGenre in toString, no query is generated and I'm just getting a null back.
When you use HQL syntax (e.g. createQuery("from Movie")), then Hibernate/JPA will only fetch the Genre entity when you call getGenre() on your Movie object. This is called "lazy fetching". When the method is called, Hibernate will issue another query to fetch the Genre.
Note that HQL queries ignore the FetchType on your annotations - HQL is used to tell Hibernate exactly what to do, rather than using the hints in the annotations.
To make it fetch the Genre in the same query as the Movie, you need to tell it to:
createQuery("from Movie m join fetch m.genre")
try this:
Moive side:
#ManyToOne
#JoinColumn(name = "genre_id")
public Genre getGenre() {..}
and on the other side (genre):
#OneToMany(mappedBy="genre")
List/Set getMovies(){..}
then you can from a movie object movie.getGenre() get Genre.

Java EE - One to many bidirectional

To my understanding a Unidirectional one-to-many relationship has a join table and a Bidirectional one-to-many relationship hasn't.
My application works when I implemented the Unidirectional relationship but I can't seem to get it to work for the bidirectional relationship.
These are the tables I am creating
CREATE TABLE CUSTOMER (customerNo INTEGER PRIMARY KEY, joinDate DATE, customerName VARCHAR2(20));
CREATE TABLE BOOKING (bookingNo INTEGER PRIMARY KEY, bookedDate DATE, custNo INTEGER, itemNo NUMBER(10), itemName VARCHAR2(20), quantity NUMBER(5), bookingDate DATE, bookingValue NUMBER(8, 2), constraint booking_fk foreign key (custNo) references customer(customerNo));
My first class
public class Booking implementes Serializable{
private Timestamp bookeddate;
private Timestamp bookingdate;
#Id()
#GeneratedValue(generator"MY_SEQ_GEN")
#SequenceGenerator(name="MY_SEQ_GEN", sequenceName="MY_SEQUENCE", allocationSize=1)
#Column(name="bookingNo", nullable=false)
private Long bookingno;
private Double bookingvalue;
#Column(length = 20)
private String itemname;
private Long itemno;
private Long quantity;
private Customer customer;
#ManyToOne
public Customer getCustomer() {
return customer;
}
...
my other class
public class Customer implements Serializable {
#Column(length = 20)
private String customername;
#Id()
#GeneratedValue(generator="THE_SEQ_GEN")
#SequenceGenerator(name="THE_SEQ_GEN", sequenceName="THE_SEQUENCE", allocationSize=1)
#Column(name="customerNo", nullable=false)
private Long customerno;
private Timestamp joindate;
#OneToMany(cascade=(CascadeType.ALL), fetch=FetchType.EAGER, mappedBy = "customer")
private List<Booking> bookings = new ArrayList<Booking>();
public List<Booking> getBookings() {
return bookings;
}
my bean falls over after I run this method
public void addBooking(Long custno, Long tickno, Long quantity) {
Customer cust = (Customer) em.createNamedQuery("findCustomerByPrimaryKey").setParameter("eid", custno).getSingleResult();
Booking b = new Booking();
b.setBookeddate(new Timestamp(System.currentTimeMillis()));
b.setCustomer(cust);
b.setTicket((Ticket) em.createNamedQuery("findTicketByPrimaryKey").setParameter("eid", tickno).getSingleResult(), quantity);
cust.addBooking(b);
//persistBooking(b);
}
And this is my error message.
javax.ejb.EJBException: BEA1-001D72BA69DC9E472B1E: Local Exception Stack:
Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.0.2.v20100323-r6872): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: java.sql.SQLSyntaxErrorException: ORA-00904: "CUSTOMER_CUSTOMERNO": invalid identifier
Error Code: 904
Call: INSERT INTO BOOKING (bookingNo, ITEMNAME, BOOKINGDATE, BOOKINGVALUE, ITEMNO, QUANTITY, BOOKEDDATE, CUSTOMER_customerNo) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
bind => [71, Metallica, 2011-01-05 22:07:17.788, 200.0, 420, 2, 2011-01-05 22:07:17.788, 1526]
Query: InsertObjectQuery(courseworkone.Booking#201a41)
at org.eclipse.persistence.exceptions.DatabaseException.sqlException(DatabaseException.java:324)
at org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor.executeDirectNoSelect(DatabaseAccessor.java:801)
Your customer mapping is using the default column name which is "CUSTOMER_CUSTOMERNO", but you have created your table using "custNo". You need to use the correct column name.
i.e.
#ManyToOne
#JoinColumn(name="custNo")
public Customer getCustomer() {
return customer;
}

Categories