# Notes
# Collections of Basic Types
@CollectionTable
@ElementCollection
(Goncalves Ex23)- Useful for storing Set or List of basic types such as Strings or Integers
@MapKeyColumn
(Goncalves Ex24)- Useful for storing Map of basic types such as
<Integer,String>
# Relationship Mapping
- Entities have relationships with other entities
- Direction or Navigation
- Unidirectional 单向
- Bidirectional 双向
- Multiplicity or Cardinality
- One-to-one
- One-to-many
- Many-to-one
- Many-to-many
从左到右读,Many Appointment to One Pet
# Unidirectional
- Has only an owning side 只有一面
- In this example, you can only access customer addresses via the Customer entity (the owner)
在此示例中,您只能通过客户实体访问客户地址
# Bidirectional
- Has both an owning and an inverse side
- In this example, you can access addresses from the customer entity, but you can also access customers from the address entity
在此示例中,您可以从客户实体访问地址,但也可以从地址实体访问客户
# One-to-One
Each entity instance is related to a single instance of another entity.
每个实体实例都与另一个实体的单个实例相关。
@OneToOne
Unidirectional- Annotation goes on owning side
- FK is on owning entity’s table
- Use
@JoinColumn
on owner to customize FK- Goncalves Ex34 (CBE)
- Goncalves Ex39 (with
@JoinColumn
)
@OneToOne
Bidirectional- Annotation goes on both sides
- Inverse side uses mappedBy to point to owner
- FK is on owning entity’s table
- Use
@JoinColumn
on owner to customize FK - Application must manage both sides of the relationship
- Can be done programmatically
- Can be done through setter methods
# One-to-Many
An entity instance can be related to multiple instances of the other entities.
一个实体实例可以与其他实体的多个实例相关。
@JoinTable
(Goncalves Ex43)- name
- joinColumns
- inverseJoinColumns
@OneToMany
Unidirectional- Annotation goes on owning side
- Defaults to join table
- Use
@JoinTable
on owner to customize FKs in join table- joinColumns
- inverseJoinColumns
# Many-to-One
Multiple instances of an entity can be related to a single instance of the other entity. This multiplicity is the opposite of a one-to-many relationship.
一个实体的多个实例可以与另一个实体的单个实例相关。这种多样性与一对多关系相反。
@JoinColumn
@ManyToOne
Unidirectional- Annotation goes on owning side (always the many side for ManyToOne)
- Defaults to join column (FK on the many side)
- Use
@JoinColumn
on owner to customize FK
@ManyToOne
and@OneToMany
Bidirectional- Annotation goes on both sides
- Inverse (OneToMany) side uses mappedBy to point to owner
- Defaults to join column (FK on many side)
- Use
@JoinColumn
on owner to customize FK - Application must manage both sides of the relationship
- Can be done through setter and add methods
# Many-to-Many
The entity instances can be related to multiple instances of each other.
@ManyToMany
- Goncalves Ex46
@JoinTable
with joinColumns and inverseJoinColumns, and mappedBy on inverse Annotation goes on both sides- Use
@JoinTable
on owner to customize FKs in join table- joinColumns
- inverseJoinColumns
- Inverse side uses mappedBy to point to owner
- Application must manage both sides of the relationship
- Can be done through add methods – remember these are collections
# Relationship Mapping Tips
- Navigating relationships on a detached entity will cause exceptions
在独立实体上导航关系将导致异常 - Application bears the responsibility of maintaining relationships between objects
应用程序负责维护对象之间的关系- We can use setters and add methods
我们可以使用设置器和添加方法
- We can use setters and add methods
- Don’t forget to initialize collections!
不要忘记初始化集合! - Table/Entity with FK is the owner
# Bidirectional Relationship Rules
The inverse side of a bidirectional relationship must refer to its owning side by using the mappedBy element of the
@OneToOne
,@OneToMany
, or@ManyToMany
annotation. The mappedBy element designates the property or field in the entity that is the owner of the relationship.The many side of many-to-one bidirectional relationships must not define the mappedBy element. The many side is always the owning side of the relationship.
For one-to-one bidirectional relationships, the owning side corresponds to the side that contains the corresponding foreign key.
For many-to-many bidirectional relationships, either side may be the owning side.
@Entity | |
@Table(name = "PET") | |
@NamedQuery(name = "Pet.findPetByName", query = "select p from Pet p where p.name = :NAME") | |
@NamedQuery(name = "Pet.findAll", query = "select p from Pet p") | |
public class Pet { | |
@Id | |
@GeneratedValue(strategy = GenerationType.IDENTITY) | |
private Long id; | |
@NotBlank | |
@Column(name = "PET_NAME", nullable = false, unique = true) | |
private String name; | |
@PastOrPresent | |
private LocalDate birthDate; | |
@Transient | |
private Integer age; | |
@Enumerated(EnumType.STRING) | |
private PetType type; | |
private Boolean adopted; | |
// bi-directional relationship - non-owning (inverse) side | |
@ManyToMany(mappedBy = "adoptedPets") | |
private List<Adopter> owners = new ArrayList<>(); | |
··· | |
} |
@Entity | |
public class Adopter { | |
··· | |
// bi-directional relationship - non-owning (inverse) side | |
@OneToMany(mappedBy = "adopter") | |
private List<Appointment> appointments = new ArrayList<>(); | |
// bi-directional relationship - owning side | |
@ManyToMany | |
@JoinTable(name = "ADOPTED_PETS", | |
joinColumns = @JoinColumn(name = "ADOPTER_ID"), | |
inverseJoinColumns = @JoinColumn(name = "PET_ID")) | |
private List<Pet> adoptedPets = new ArrayList<>(); | |
public Adopter() { | |
} | |
// helper methods for collections | |
public void addAdoptedPet(Pet p) { | |
if (!this.adoptedPets.contains(p)) { | |
this.adoptedPets.add(p); | |
} | |
if (!p.getOwners().contains(this)) { | |
p.getOwners().add(this); | |
} | |
} | |
public void removeAdoptedPet(Pet p) { | |
if (this.adoptedPets.contains(p)) { | |
this.adoptedPets.remove(p); | |
} | |
if (p.getOwners().contains(this)) { | |
p.getOwners().remove(this); | |
} | |
} | |
··· | |
} |
@Entity | |
public class Vet { | |
··· | |
// bi-directional relationship - non-owning (inverse) side | |
@OneToMany(mappedBy = "vet") | |
private List<Appointment> appointments = new ArrayList<>(); | |
public Vet() { | |
} | |
··· | |
} |
@Entity | |
public class Appointment { | |
··· | |
// uni-directional relationship | |
@ManyToOne | |
private Pet pet; | |
// bi-directional relationship - owning side | |
@ManyToOne | |
private Vet vet; | |
// bi-directional relatinoship - owning side | |
@ManyToOne | |
private Adopter adopter; | |
public Appointment() { | |
} | |
··· | |
} |
# Ordering Relationships
@OrderBy
(Goncalves Ex49)- No effect on database schema
@OrderColumn
(Goncalves Ex51)- Impacts database schema
# Inheritance
# Inheritance Mappings 继承映射
SINGLE_TABLE
strategy- A single table per class hierarchy
@DiscriminatorValue
(Goncalves Ch5 Ex56)
JOINED
strategy (Goncalves Ch5 Ex59)TABLE_PER_CLASS
strategy (Ch5 Ex60)- This strategy is not required by the JPA specification. Avoid for more portable code.
JPA 规范不需要此策略。避免使用更多可移植的代码。
- This strategy is not required by the JPA specification. Avoid for more portable code.
# Inheritance Hierarchy 继承层次
- Entities can inherit from
实体可以继承- Entity superclasses (as we have just seen with inheritance mapping)
实体超类(就像我们在继承映射中看到的那样) - Nonentity superclasses (Goncalves Ch5 Ex63)
非实体超类
- Entity superclasses (as we have just seen with inheritance mapping)
@MappedSuperclass
(Goncalves Ch5 Ex66)
- Contain persistent state and mappings, but are not entities
包含持久状态和映射,但不是实体
@MappedSuperclass | |
public class AbstractEntity { | |
@Id | |
@GeneratedValue(strategy = GenerationType.IDENTITY) | |
protected Long id; | |
@HotSpotIntrinsicCandidate | |
public AbstractEntity() { | |
} | |
public Long getId() { | |
return id; | |
} | |
public void setId(Long id) { | |
this.id = id; | |
} | |
@Override | |
public int hashCode() { | |
int hash = 5; | |
hash = 71 * hash + Objects.hashCode(this.id); | |
return hash; | |
} | |
@Override | |
public boolean equals(Object obj) { | |
if (this == obj) { | |
return true; | |
} | |
if (obj == null) { | |
return false; | |
} | |
if (getClass() != obj.getClass()) { | |
return false; | |
} | |
final AbstractEntity other = (AbstractEntity) obj; | |
// because I am using a database generated ID, I need to explicitly check entity id's for null | |
// if null, they should not be compared | |
if ((this.id == null) || (other.id == null)) { | |
return false; | |
} | |
if (!Objects.equals(this.id, other.id)) { | |
return false; | |
} | |
return true; | |
} | |
} |
# Cascading 级联
Cascade | Type Description |
---|---|
| Cascades persist operations to the target of the association |
| Cascades remove operations to the target of the association |
| Cascades merge operations to the target of the association |
| Cascades refresh operations to the target of the association |
| Cascades detach operations to the target of the association |
| Declares that all the previous operations should be cascaded |
# Concurrency 并发
Versioning
@Version
read-only attribute- Not required, but recommended if the entity can be modified concurrently by more than one process or thread
- Automatically enables optimistic locking
Locking
- Optimistic – Obtain lock before commit
- Pessimistic – Very resource intensive
Goncalves Chapter 6 Example 36
# Optimistic Locking
# Callbacks
Annotation | Description |
---|---|
@PrePersist | Marks a method to be invoked before EntityManager.persist() is executed. |
@PostPersist | Marks a method to be invoked after the entity has been persisted. If the entity autogenerates its primary key (with @GeneratedValue ), the value is available in the method. |
@PreUpdate | Marks a method to be invoked before a database update operation is performed(calling the entity setters or the EntityManager.merge() method). |
@PostUpdate | Marks a method to be invoked after a database update operation is performed. |
@PreRemove | Marks a method to be invoked before EntityManager.remove() is executed. |
@PostRemove | Marks a method to be invoked after the entity has been removed. |
@PostLoad | Marks a method to be invoked after an entity is loaded (with a JPQL query or an EntityManager.find() ) or refreshed from the underlying database |
# Entity Listeners
Can be used to extract the business logic to a class for re-use among multiple entities.
可用于将业务逻辑提取到一个类中,以便在多个实体之间重用。
Goncalves Chapter 6 Examples 39 and 42
# Callback Methods
Work well when you have business logic only related to that entity.
当您仅具有与该实体相关的业务逻辑时,可以很好地工作。
Goncalves Chapter 6 Example 38
# Lab
# Summary
The purpose of this assignment is to expand our final project business domain with additional entities, implement relationships within our business domain, explore the JPA requirements for applications to manage both sides of bi-directional relationships, and demonstrate with JUnit test cases. Finally, this project also incorporates both a maven web application and JUnit testing within the same project, demonstrating the use of two separate persistence units, one JTA (for use by Payara while running our web application) and one RESOURCE_LOCAL (from use by Java SE while running unit tests).
# Requirements
# Documentation
You can take a break from the wiki this week. No docs required for this lab. I will be asking you to write about your Final Project design in detail, including the relationships between your entities, on the written Midterm - so use this Lab to experiment with your relationships, and document the design on the written Midterm question.
# Database Setup
Use your itmd4515 database and user from Lab 2 - Setup and Introductory Webapp.
# Project Setup
Your uid-fp repository should already be setup, and you should continue pushing your commits into GitHub for this lab.
Deviating from the package convention given above will mean that you can not benefit from Sonar and other automated tools, and I will not be able to fix this. Please follow the specification!
We will be working in this repository from now until the end of the semester. Please remember, I will be looking for multiple commits. I would suggest using the lab number in your commit message as a prefix so you can also review the history throughout the semester, for example:
- Lab 6 - Initial Commit
- Lab 6 - OneToOne unidirectional relationship between Foo and Bar
- Lab 6 - Test cases for unidirectional 1:1 relationship between Foo and Bar
- Lab 6 - ManyToMany bidirectional relationship between Widget and Gizmo
- Lab 6 - Test cases for bidirectional M:M relationship between Widget and Gizmo
# Project Requirements
If you haven't already (from Lab 4 - Web Applications, Servlet and JSP), define a JDBC Resource for use by your application. There are many many ways to do this:
- Creating a Payara JDBC Connection Pool and JDBC Resource via the admin console
- Creating a Payara JDBC Connection Pool and JDBC Resource via the asadmin command line utility
- Defining a JDBC Resource directly in Payara domain configuration file
- Defining a JDBC Resource via web.xml
- Defining a JDBC Resource via annotations in our code (this is what we are going to do. the others might be more useful if you are hosting multiple applications in one server that could share a data source)
Your
@DataSourceDefinition
should look something like below If you have multiple parameters in your JDBC URL, you would include them as parameters via the annotation Note - depending your operating system, you may also need to adduseSSL=false
.Important - If you are re-using your DataSourceDefinition from Lab 4, make sure you don't forget to change the databaseName to itmd4515 like me!
@DataSourceDefinition(
name = "java:app/jdbc/itmd4515DS",
className = "com.mysql.cj.jdbc.MysqlDataSource",
portNumber = 3306,
serverName = "localhost",
databaseName = "itmd4515",
user = "itmd4515",
password = "itmd4515",
properties = {
"zeroDateTimeBehavior=CONVERT_TO_NULL",
"serverTimezone=America/Chicago",
"useSSL=false"
}
Consider the deployment of your MySQL JDBC Driver. Assuming you are also using a
@DataSourceDefinition
, you should be able to include the JDBC driver with your application by ensuring it has default scope in your pom.xml as I am showing in class. Alternatively, you can copy the MySQL JDBC jar file to your Payara domain's lib/ext (extensions) folder.Move your RESOURCE_LOCAL Persistence Unit from Lab 5 - ORM and JPA named itmd4515testPU to an appropriate location for testing. In class, I will demonstrate that you can create this manually in the Files view by creating a
src/test/resources
folder, and then moving yourpersistence.xml
. This may also necessitate specifying entities within the persistence unit - I will demonstrate this in class.Create a new JTA Persistence Unit named itmd4515PU connecting to your itmd4515 database using the itmd4515 user. NetBeans seems to have lost the ability to create a JTA
persistence.xml
, so create this manually by copying your testpersistence.xml
to the right location and modifying as shown in class.Once complete, you should have two
persistence.xml
files:src/main/resources/META-INF/persistence.xml
andsrc/test/resources/META-INF/persistence.xml
Make sure the Persistence Unit defined in each file has a different name, and that your test class refer to the itmd4515testPU PU. We will move forward with test cases pointing to a "test" PU, and the web application pointing to a "production" PU.
Make sure you double-check your
pom.xml
in case the NetBeans persistence wizard added an additional JPA dependency. if so, remove it. Your dependencies should continue to look like Lab 5 - ORM and JPA.code
src/main/resources/META-INF/persistence.xml <?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.2" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd">
<!-- Define Persistence Unit -->
<persistence-unit name="itmd4515PU" transaction-type="JTA">
<jta-data-source>java:app/jdbc/itmd4515DS</jta-data-source>
<exclude-unlisted-classes>false</exclude-unlisted-classes>
<properties>
<property name="javax.persistence.schema-generation.database.action" value="drop-and-create"/>
</properties>
</persistence-unit>
</persistence>
src/test/resources/META-INF/persistence.xml <?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.2" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd">
<!-- Define Persistence Unit -->
<persistence-unit name="itmd4515testPU" transaction-type="RESOURCE_LOCAL">
<class>edu.iit.sat.itmd4515.sliu136.domian.Album</class>
<exclude-unlisted-classes>false</exclude-unlisted-classes>
<properties>
<property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/itmd4515?zeroDateTimeBehavior=CONVERT_TO_NULL"/>
<property name="javax.persistence.jdbc.user" value="itmd4515"/>
<property name="javax.persistence.jdbc.driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="javax.persistence.jdbc.password" value="itmd4515"/>
<property name="javax.persistence.schema-generation.database.action" value="drop-and-create"/>
</properties>
</persistence-unit>
</persistence>
If necessary, re-factor your entity classes so that they reside in a dedicated domain or model sub-package. If necessary, update your test class(es) to reside in the same package.
Based on your design thoughts from Lab 5 - ORM and JPA, introduce additional entities to your business domain. The requirements are:
You must have at least four total entities in your business domain. You may have as many as you want, but at least four.
Remember, your final project must support the logical concept of multiple types of users or roles, but do not create these as entities. We must consider this now so we can introduce security in later projects, and we will use the JEE security framework to introduce these security entities later. Likewise, do not create Admin as an entity. Stay focused on entities within your domain.
As before, all your entities should:
Use an appropriate PK strategy, and use an appropriate data type for your PK (as per the options discussed in class)
Include appropriate equals and hashCode methods for your PK strategy
Include at least one temporal data type
Include at least three different data types. There is no limit to the number of attributes you can include. Your attributes should be sufficient to represent your entity. Exercise good design and judgment.
Include appropriate Constructors, accessors and mutators
Include appropriate bean validation constraints based on your database types and sizes
Include appropriate toString method for formatted output
In other words, your entities should make sense. Do not use mine from class demo. I am coding fast and furious, to try and demonstrate all you need for 1 week in a few hours.
Include at least three relationships between your entities
- At least one of the relationships must be bi-directional
- Try and use different relationship types. I will not look favorably on 3 OneToOne uni-directional relationships. This is for you to learn about relationships, and to learn about relationships you need to introduce some real relationships into your business domain.
Include appropriate helper methods in your business domain to manage both sides of the relationship. Will be demonstrated in class.
Two JUnit test cases to illustrate that your relationships are working as expected:
- One Uni-directional relationship test case
- One Bi-directional relationship test case
- Consider the example I worked through in class. You can assert that persist was successful, and that collections on both sides of a bi-directional relationship contain the expected entity.
code
@Test
public void testAdopterPetManyToManyBiDirectionalRelationship(){
// create entities to test
Pet cat = new Pet("Fluffy", LocalDate.of(2020, Month.DECEMBER, 12), PetType.FELINE);
Adopter adopter = new Adopter("Scott", "Spyrison", LocalDate.of(1950, Month.MARCH, 12));
// ex 1 - I demonstrated, without any relationship management these are just two indepednent entities with no relationship
// ex 2 - Add the adopter to the inverse side of the relationship. If you only do this, and you don't manage the owning side, then you will have inconsistent database updates
//cat.getOwners().add(adopter);
// ex 3 - Manage the owning side, but skip the non-owning inverse side
//adopter.getAdoptedPets().add(cat);
// ex 4 - Manage both sides of the relationship per JPA requirements
// and Prof Scott lecture!
// adopter.getAdoptedPets().add(cat);
// cat.getOwners().add(adopter);
adopter.addAdoptedPet(cat);
tx.begin();
em.persist(cat);
em.persist(adopter);
tx.commit();
adopter = em.find(Adopter.class, adopter.getId());
cat = em.find(Pet.class, cat.getId());
// ex 3 continued - let's explore the persistence context for this relationship
// this is navigation from the OWNING side
//System.out.println("OWNING SIDE: " + adopter.getAdoptedPets().toString());
//for( Pet p : adopter.getAdoptedPets()){
// this is navigation from the INVERSE side
//System.out.println("INVERSE SIDE: " + p.getOwners().toString());
//}
// ex 3 fails in the PersistenceContext because we didn't manage both sides of the relationships.
// PersistenceContext is out of sync with the database update
// ex 4
adopter = em.find(Adopter.class, adopter.getId());
// this is navigation from the OWNING side
System.out.println("OWNING SIDE: " + adopter.getAdoptedPets().toString());
for( Pet p : adopter.getAdoptedPets()){
// this is navigation from the INVERSE side
System.out.println("INVERSE SIDE: " + p.getOwners().toString());
}
// ex 4 - our database update is consistent with both sides of our relationship
// and persistence context is in sync with database
// travel both sides of the relationship and assert what we find is expected
assertTrue(adopter.getAdoptedPets().size() == 1);
assertTrue(cat.getOwners().size() == 1);
assertEquals(cat.getName(), adopter.getAdoptedPets().get(0).getName());
assertEquals(adopter.getId(), cat.getOwners().get(0).getId());
// clean up after ourselves - remove test data
tx.begin();
// first, remove from the collection
adopter.removeAdoptedPet(cat);
// then, remove the non-owning entity
em.remove(cat);
// finally, remove the owning entity
em.remove(adopter);
tx.commit();
}
Please feel free to use Sonar to analyze your code before submitting. I have created Sonar projects for everyone.
Submit to Blackboard
- Right your uid-fp project and select "Clean"
- Go to your NetBeans Projects directory. Create a zip file of the uid-fp folder and submit it to the Blackboard assignment.
# Extra Credit
Graduate Students must also introduce JPA functionality to your existing web application by refactoring your Servlet from Lab 4 - Web Applications, Servlet and JSP to use an EntityManager and UserTransaction instead of JDBC. This will require the following:
- Change your Lab 4 POJO to an Entity.
- In most cases this will be as simple as adding an
@Entity
and@Id
annotation to the class! - If you run into sample database complications with the primary key, just add an auto-incrementing
@Id
field to the class. - Don't over-think this part - I just want you to get the experience of using the
@PersistenceConext
annotation in your existing Servlet, and I want you to see how much easier it is toem.persist
than use all thatPreparedStatement
code!
- In most cases this will be as simple as adding an
- Obtain an
EntityManager
using@PersistenceContext
injection, and aUserTransaction
usingresource
injection. - If user input from your form passes validation in your Servlet controller, persist the entity to the database. How easy is that? Easier than JDBC?
By the end of your refactoring, you should not have any JDBC code left in your project, nor should you be using the @DataSource
annotation. Will demo in class.