# 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
      我们可以使用设置器和添加方法
  • 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.

Pet.java
@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<>();
    ···
}
Adopter.java
@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);
        }
    }
    ···
}
Vet.java
@Entity
public class Vet {
    ···
    // bi-directional relationship - non-owning (inverse) side
    @OneToMany(mappedBy = "vet")
    private List<Appointment> appointments = new ArrayList<>();
    public Vet() {
    }
    ···
}
Appointment.java
@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 规范不需要此策略。避免使用更多可移植的代码。

# Inheritance Hierarchy 继承层次

  • Entities can inherit from
    实体可以继承
    • Entity superclasses (as we have just seen with inheritance mapping)
      实体超类(就像我们在继承映射中看到的那样)
    • Nonentity superclasses (Goncalves Ch5 Ex63)
      非实体超类

@MappedSuperclass (Goncalves Ch5 Ex66)
- Contain persistent state and mappings, but are not entities
包含持久状态和映射,但不是实体

AbstractEntity.java
@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 级联

CascadeType Description

PERSIST

Cascades persist operations to the target of the association
级联将操作保持到关联目标

REMOVE

Cascades remove operations to the target of the association
级联删除对关联目标的操作

MERGE

Cascades merge operations to the target of the association
级联将操作合并到关联的目标

REFRESH

Cascades refresh operations to the target of the association
级联刷新操作以达到关联目标

DETACH

Cascades detach operations to the target of the association
级联将操作分离到关联目标

ALL

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

AnnotationDescription
@PrePersistMarks a method to be invoked before EntityManager.persist() is executed.
@PostPersistMarks 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.
@PreUpdateMarks a method to be invoked before a database update operation is performed(calling the entity setters or the EntityManager.merge() method).
@PostUpdateMarks a method to be invoked after a database update operation is performed.
@PreRemoveMarks a method to be invoked before EntityManager.remove() is executed.
@PostRemoveMarks a method to be invoked after the entity has been removed.
@PostLoadMarks 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

  1. 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:

    1. Creating a Payara JDBC Connection Pool and JDBC Resource via the admin console
    2. Creating a Payara JDBC Connection Pool and JDBC Resource via the asadmin command line utility
    3. Defining a JDBC Resource directly in Payara domain configuration file
    4. Defining a JDBC Resource via web.xml
    5. 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)
  2. 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 add useSSL=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"
        }
  3. 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.

  4. 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 your persistence.xml .  This may also necessitate specifying entities within the persistence unit - I will demonstrate this in class.

  5. 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 test persistence.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 and src/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>
  6. 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.

  7. Based on your design thoughts from Lab 5 - ORM and JPA, introduce additional entities to your business domain.  The requirements are:

    1. You must have at least four total entities in your business domain.  You may have as many as you want, but at least four.

    2. 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.

  8. As before, all your entities should:

    1. Use an appropriate PK strategy, and use an appropriate data type for your PK (as per the options discussed in class)

    2. Include appropriate equals and hashCode methods for your PK strategy

    3. Include at least one temporal data type

    4. 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.

    5. Include appropriate Constructors, accessors and mutators

    6. Include appropriate bean validation constraints based on your database types and sizes

    7. Include appropriate toString method for formatted output

    8. 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.

  9. Include at least three relationships between your entities

    1. At least one of the relationships must be bi-directional
    2. 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.
  10. Include appropriate helper methods in your business domain to manage both sides of the relationship.  Will be demonstrated in class.

  11. Two JUnit test cases to illustrate that your relationships are working as expected:

    1. One Uni-directional relationship test case
    2. One Bi-directional relationship test case
      1. 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();
        
    }
  12. Please feel free to use Sonar to analyze your code before submitting.  I have created Sonar projects for everyone.

  13. Submit to Blackboard

    1. Right your uid-fp project and select "Clean"
    2. 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:

  1. Change your Lab 4 POJO to an Entity.
    1. In most cases this will be as simple as adding an @Entity and @Id annotation to the class!
    2. If you run into sample database complications with the primary key, just add an auto-incrementing  @Id field to the class.
    3. 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 to em.persist than use all that PreparedStatement code!
  2. Obtain an EntityManager using @PersistenceContext injection, and a UserTransaction using resource injection.
  3. 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.