I don't have yet any knowledge relating ORM, Hibernate, JPA and such, so is this a valid way of mapping associations between entities in database, without using them ?
Thanks.
User can register a company.
Each company has many departments.
Each department has many sectors, and so on with offices, employees etc.
public class Company{
private String company_name;
private int company_registration_number;
private String company_address;
public Company(String company_name, int crn, String company_address) {
this.company_name = company_name;
this.company_registration_number = crn;
this.company_address = company_address;
}
/* Getters, Setters, toString */
}
Department class
public class Department {
private String dept_name;
private String dept_description;
public Department( String dept_name, String dept_description) {
this.dept_name = dept_name;
this.dept_description=dept_description;
}
/*Getters, Setters, toString*/
}
CompanyDB
public class CompanyDB {
public boolean createCompany(Company company) throws SQLException {
String sql_query = "INSERT INTO " + DB_NAME + ".company VALUES (?, ?, ? )";
}
//other crud methods
}
DepartmentDB
public class DepartmentDB {
public boolean createDepartment(Department department, String company_name) throws SQLException {
String sql_query = "INSERT INTO " + DB_NAME +
".department(dept_name, dept_description, company_name)" +
" VALUES(?, ?, ?) " +
" WHERE company_name=?";
}
//other crud methods
}
SQL:
create table `company`(
`company_name` varchar(250),
`company_registration_number` int not null,
`company_address` varchar(250) not null,
primary key(`company_name`)
);
create table `department` (
`dept_id` int auto_increment,
`dept_name` varchar(250) not null unique,
`dept_description` varchar(512) not null,
`company_name` varchar(250),
primary key(`dept_id`) ,
foreign key(`company_name`) references `company`(`company_name`)
);
You may want to check out Hibernate relations or annotations as this could make your life a lot easier.
See
https://docs.jboss.org/hibernate/orm/3.3/reference/en/html/associations.html
or https://docs.jboss.org/hibernate/stable/annotations/reference/en/html_single/ for some basics.
It seems like you've got the basics down but Hibernate/JPA provides the functionality such that you only have to create the database tables and relations or the entities with mapping and it will automatically generate the other half for you.
An example of what you are trying to do
public class Company {
#Id
private Long id;
#Column(name = "company_name")
private String companyName;
#Column(name = "company_registration_number")
private int companyRegistrationNumber;
#Column(name = "company_address")
private String companyAddress;
#OneToMany
private Set<Department> departments;
/* Getters, Setters, toString */
}
and
public class Department {
#Id
private Long id;
#Column(name = "dept_name")
private String deptName;
#Column(name = "dept_description")
private String deptDescription;
#ManyToOne
private Company company;
/*Getters, Setters, toString*/
}
In this case Hibernate will automatically associate the Company with all of the Departments by creating a column on the Department table (on the primary key for the company) so that it can associate all departments with said Company.
For JPA entities mapping is accomplished by annotations, also tables may be generated by Hibernate plugin please read about this. This link may help http://docs.jboss.org/tools/latest/en/hibernatetools/html/plugins.html
Related
I'm strugling with JPA. I tried several things but I can't figure out the right way to put the annotations.
What is want is like an Order/OrderLine relationship.
Thus:
Order( PK=orderId, fields=[...])
OrderLine (Pk1=orderId,Pk2=orderLineId, fields=[...])
Obviously, OrderLine.orderId refers to the 'Order' table.
What I functionally want to do is at least:
retrieve the Order with and without all orderlines. It should have a Set
retrieve an orderline by full PK, but without the associated Order.
retrieve a list of orderlines by orderId.
I only want these 2 tables and classes. nothing more nothing less.
I tried several things. Can anybody help me out with putting in the right annotations and members on these two classes?
Edit: what i've done so far.
Note that in this real example User=Order and UserRun=OrderLine. So, i am not interested in a seperate 'Run'-entity. Merely a UserRun as described by the Orderline.
#Entity
#Table(name = "user_runs")
public class UserRun {
#EmbeddedId
private UserRunKey id;
public UserRun(){};
public UserRun(String userName, String runUuid) {
this.id = new UserRunKey(userName, runUuid);
}
public String getUserName() {
return this.id.getUserName();
}
public String getRunUuid() {
return this.id.getRunUuid();
}
}
#Embeddable
class UserRunKey implements Serializable {
#Column(name = "username")
private String userName;
#Column(name = "run_uuid")
private String runUuid;
public UserRunKey(){};
public UserRunKey(String userName, String runUuid) {
this.runUuid = runUuid;
this.userName = userName;
}
public String getUserName() {
return userName;
}
public String getRunUuid() {
return runUuid;
}
}
This created a userruns/orderline table with the PK in the wrong way:
create table user_runs (run_uuid varchar(255) not null, username varchar(255) not null, primary key (run_uuid, username))
I want the primary key in reverse.
I want username as FK to User
I want a Set in my User-class.
When I do the following in my User-class:
#OneToMany
private Set<UserRun> userRuns;
It will create a
create table user_user_runs (user_username varchar(255) not null, user_runs_run_uuid varchar(255) not null, user_runs_username varchar(255) not null, primary key (user_username, user_runs_run_uuid, user_runs_username))
And that's something I definitely don't want! Once again, I don't want a Run-object (same as nobody's interested in a Line-class, from OrderLine)
I think I figured it out.
The UserRun/Orderline class:
#Entity
#Table(name = "user_runs")
public class UserRun {
#EmbeddedId
private UserRunKey id;
public UserRun(){};
public UserRun(String userName, String runUuid) {
this.id = new UserRunKey(userName, runUuid);
}
public String getUserName() {
return this.id.getUserName();
}
public String getRunUuid() {
return this.id.getRunUuid();
}
}
#Embeddable
class UserRunKey implements Serializable {
#Column(name = "username")
private String userName;
#Column(name = "run_uuid")
private String zrunUuid; //starts with a z, so the PK will be pk(username,run_uuid). Apparently, order in PK is determined from the variable names (alphabetic order)....
public UserRunKey(){};
public UserRunKey(String userName, String zrunUuid) {
this.zrunUuid = zrunUuid;
this.userName = userName;
}
public String getUserName() {
return userName;
}
public String getRunUuid() {
return zrunUuid;
}
}
In the userclass:
#OneToMany(mappedBy = "id.userName", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private Set<UserRun> userRuns;
Unfortunately, there are 2 downsides:
I see that there are 2 queries executed instead of a Join on username. One to retrieve user, and 1 to retrieve the Set...
I needed to alter variablenames of the PK (compound/Embeddable). It seems there is no clean way to define the PK order. (Seriously?). Fortunately, the variable name is private, and not exposed by getter.
If anybody knows a cleaner way for these 2 issues. Let me know!
I think what you have to do is the following:
Because the primary key is compound key you need an ID class, as you already did:
#Embeddable
class OrderLinePK implements Serializable {
// you can use physical mapping annotations such as #Column here
#Column(name="...")
private Integer orderLineID;
// This is foreign key and the physical mapping should be done
// on the entity, and not here
private Integer orderID;
public OrderLinePK(){}
// getters + setters
// orverride equals() and hashCode() methods
}
Implement OrderLine entity
#Entity
public class OrderLine {
#EmbededId private OrderLinePK id;
#Mapsid("orderID")
#ManyToOne
#JoinColumn(name = "ORDER_ID", referencedColumn="ID")
private Order order;
// getters + setters ....
}
And the Order entity:
#Entity
public class Order {
#Id
private Integer id;
#OneToMany(fetch = FetchType.LAZY) // actually default by 1-to-n
private Coolection<OrderLine> orderLines;
// getters + setters ....
}
I having issues in mapping a mysql SET type to Java Set using JPA
To illustrate my question i frame a random example below
Here is a table which has a column genre which is of type Set (i.e:it will be a collection of Strings)
CREATE TABLE `MusicCD` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`period` ENUM('Classical', 'Modern','Antique') NOT NULL,
`genre` SET('horror','thriller','comedy','drama','romance') ,
PRIMARY KEY (`id`)
)
Below is the entity class used for the mapping
#Entity
#Table(name = "MusicCD")
class MusicCD {
private long id;
private Period period;
private Set<String> genre;
//other getter setters //
#Column(name = "genre")
#ElementCollection(targetClass = String.class, fetch = FetchType.EAGER)
public Set<String> getGenre() {
return genre;
}
public void setGenre(Set<String> genre) {
this.genre = genre;
}
}
With this mapping there is no exception but the set is empty in the entity object because the get query sent by JPA/hibernate sents query for all fields in table MusicCD but for the genre it sends a separate query to table MusicCD_genre
When i see the sql schema there is a autogenerated table MusicCD_genre which is empty.
Sending a sql select query for genre on MusicCD returns the genres.
So how does the Set data type in sql work and what is the correct annotation to map it?
Update:
I also tried
#TypeDefs({#TypeDef(name = "javaSet", typeClass = HashSet.class)})
and annotate the getter with
#Type(type = "javaSet")
but this doesn't work with EOFException during de-serialization.
This might work by replacing the HashSet with correct type to deserialize to.
I know it's an old question, but I prefer treat these ´MySQL special type´ columns in the getters/setters when the most use of them would be in java code.
#Entity
#Table(name = "MusicCD")
class MusicCD {
/*...*/
#Column(name = "genre")
private String genreStr;
/*...*/
public Set<String> getGenre() {
if(genreStr == null)
return Collections.emptySet();
else
return Collections.unmodifiableSet(
new HashSet<String>(Arrays.asList(genreStr.split(",")))
);
}
public void setGenre(Set<String> genre) {
if(genre == null)
genreStr = null;
else
genreStr = String.join(",", genre);
}
}
I use the immutable version of Set, because that avoids trying alter the set values without actually alter the DB.
I am using JPA with Hibernate 4.x., and postgresql 9.x, but I wonder some problem.
When using #ElementCollection annotation, and I configured Set type embedded field, I expect to generate Primary key of embeddable Collection Table, but it's not working. I can find just one foreign key against owner of relationship. i wonder why do not it generate primary key. This is my test code.
A.java
#Entity
public class A {
#Id
private Long id;
#Column(name = "\"as\"")
private String as;
public String getAs() {
return as;
}
public void setAs(String as) {
this.as = as;
}
#ElementCollection
private Set<B> bs = new HashSet<>();
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Set<B> getBs() {
return bs;
}
public void setBs(Set<B> bs) {
this.bs = bs;
}
}
B.java
#Embeddable
public class B {
private String col1;
private String col2;
public String getCol1() {
return col1;
}
public void setCol1(String col1) {
this.col1 = col1;
}
public String getCol2() {
return col2;
}
public void setCol2(String col2) {
this.col2 = col2;
}
}
the result is
Hibernate:
create table A (
id int8 not null,
"as" varchar(255),
primary key (id)
)
Hibernate:
create table A_bs (
A_id int8 not null,
col1 varchar(255),
col2 varchar(255)
)
Hibernate:
alter table A_bs
add constraint FK_rcecll1ao3brmwwnsep3iqq3p
foreign key (A_id)
references A
Let me know why it did not generate primary key?
Thanks in advance~
Because #ElementCollection is used for mapping collections of simple elements, which is not the same as entities. The only thing they need is a reference to owning entity which is properly generated by Hibernate (column A_id and foreign key (A_id) references A). Take a look at this post for more information.
If you really need a primary key in the embeddable object, consider making it a proper entity.
To generate a primary key for an embeddable collection like Set need to mark all columns of embeddable class with annotation #Column(nullable = false), due to requirement that nullable fields could not be a part of composite primary key
I have a situation where I have an entity VCenterDistributedVirtualPortgroup which extends VCenterNetwork and both entities are in a OneToMany relationship inside the entity VCenterFolder. I'm getting the following error:
Caused by: org.h2.jdbc.JdbcSQLException: NULL not allowed for column "NETWORK_TYPE"; SQL statement:
insert into folder_network (folder_type, folder_val, distributedVirtualPortgroups_type, distributedVirtualPortgroups_val) values (?, ?, ?, ?) [23502-182]
VCenterNetwork:
#Entity
#Embeddable
#Table(name="network")
#DiscriminatorColumn(name="discriminator")
#DiscriminatorValue("Network")
public class VCenterNetwork
{
#Transient
private Network network;
#Transient
private static Map<MOR, VCenterNetwork> networkMap = new TreeMap<MOR, VCenterNetwork>();
#EmbeddedId
private MOR id;
public MOR getId() {return this.id;}
public void setId(MOR id) {this.id = id;}
...
}
VCenterDistributedVirtualPortgroup:
#Entity
#Table(name="distributedvirtualportgroup")
#DiscriminatorColumn(name="discriminator")
#DiscriminatorValue("DistributedVirtualPortgroup")
public class VCenterDistributedVirtualPortgroup extends VCenterNetwork
{
#Transient
private DistributedVirtualPortgroup distributedVirtualPortgroup;
#Transient
private static Map<MOR, VCenterDistributedVirtualPortgroup> distributedVirtualPortgroupMap = new TreeMap<MOR, VCenterDistributedVirtualPortgroup>();
...
}
VCenterFolder:
#Entity
#Table(name="folder")
public class VCenterFolder
{
#Transient
private Folder folder;
#Transient
private static Map<MOR, VCenterFolder> folderMap = new TreeMap<MOR, VCenterFolder>();
#EmbeddedId
private MOR id;
public MOR getId() {return this.id;}
public void setId(MOR id) {this.id = id;}
#Embedded
#OneToMany(cascade=CascadeType.ALL)
private List<VCenterNetwork> network = new ArrayList<VCenterNetwork>();
public List<VCenterNetwork> getNetwork() {return this.network;}
public void getvirtualNetwork(List<VCenterNetwork> network) {this.network = network;}
#Embedded
#OneToMany(cascade=CascadeType.ALL)
private List<VCenterDistributedVirtualPortgroup> distributedVirtualPortgroups = new ArrayList<VCenterDistributedVirtualPortgroup>();
public List<VCenterDistributedVirtualPortgroup> getDistributedVirtualPortgroups() {return this.distributedVirtualPortgroups;}
public void setDistributedVirtualPortgroups(List<VCenterDistributedVirtualPortgroup> distributedVirtualPortgroups){this.distributedVirtualPortgroups = distributedVirtualPortgroups;}
....
}
This results in a join table that looks like the following:
FOLDER_NETWORK
FOLDER_TYPE - VARCHAR(255) NOT NULL
FOLDER_VAL - VARCHAR(255) NOT NULL
NETWORK_TYPE - VARCHAR(255) NOT NULL
NETWORK_VAL - VARCHAR(255) NOT NULL
DISTRIBUTEDVIRTUALPORTGROUP_TYPE - VARCHAR(255) NOT NULL
DISTRIBUTEDVIRTUALPORTGROUP_TYPE - VARCHAR(255) NOT NULL
I'm new to JPA, but my guess is that a join table is needed for both VCenterFolder-VCenterNetwork and VCenterFolder-VCenterDistributedVirtualPortgroup, but since VCenterDistributedVirtualPortgroup extends VCenterNetwork, it's building one join table with both relations but using a different type/val pair for each relation. I would assume that only one pair will be used at a time with the other being null. That seems to be a problem. It would seem to me that the fields should either be nullable or the two sets of type/val pairs should be merged into one.
I assume there is some way round this, but I sure don't know what it is.
MOR happens to have the members type & val, thus the names in the join table.
I solved the problem by adding the folloing JoinTable on both the network and distributedVirtualPortgroups fields:
#JoinTable
(
name="FOLDER_NETWORK",
joinColumns= {#JoinColumn(name="TYPE", referencedColumnName="TYPE"), #JoinColumn(name="VAL", referencedColumnName="VAL")},
inverseJoinColumns= {#JoinColumn(name="NETWORK_TYPE", referencedColumnName="TYPE"), #JoinColumn(name="NETWORK_VAL", referencedColumnName="VAL")}
)
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.