The labs on this site need JavaScript to run. The same content is written out below.
Fundamentals › JPA
What is an association, and what does @ManyToOne do for you?
An order needs to know who placed it. In tables, the order row writes the number of the member in one column (a foreign key) and points to the member. In Java objects, Order points directly to a Member object through a field. When an entity points to another entity like this, it is an association. If you write @ManyToOne above that field, JPA connects the two. When it saves, it takes the id out of the Member object and puts it in the foreign key column, and when it reads, it uses that number to find the Member and puts it in the field. So you can just follow the objects, as in order.getMember().getName(). This lab runs the same code, which saves an order and reads it again, on a database where (1, Kim) is already in the member table, and changes only the field declaration. You see what happens if you remove @ManyToOne, and what changes if you keep only a number (Long memberId) in the field instead of an object.
Checked against real execution · Compiled the source of all 4 combinations on Hibernate 7.4.5 and H2 2.4.240, started JPA for real, saved and read again, and checked against the results
@Entity
@Table(name = "orders")
public class Order {
@Id
private Long id;
// Association: point to the member who placed this order as an object
@ManyToOne
private Member member;
}
// Calling side: save an order, read it again, and look at the name of the member who placed it
Member kim = em.find(Member.class, 1L);
em.persist(new Order(10L, kim));
// After saving (commit) is done, read it again from the database with a new EntityManager (em2)
Order o = em2.find(Order.class, 10L);
System.out.println(o.getMember().getName());
This was checked against real execution on Hibernate 7.4.5 and H2 2.4.240 (in-memory database). Spring was not used, and Hibernate was started directly in code (the Member and Order classes were registered in a Configuration, without persistence.xml). All 4 combinations were checked: the same text as the Order source on the screen (with only the package and import lines, a constructor, and getters added) was compiled together with the Member source, and a new Java process was started. What the screen shortens is the package and import lines, the constructor and getters of Order, the Member entity (an ordinary entity with the fields id and name), and the lines that get the EntityManagers em and em2. The "calling side" on the screen is really code inside main, and the part that saves with em is between the lines that start and end (commit) a transaction. Those lines are shortened on the screen. The reading line was run after saving finished, with the newly made em2. If you read with the same EntityManager, it gives back the object you just saved from memory, not from the database, and the reading step could not be shown. (1, Kim) in the member table was put in directly with JDBC right after JPA started and made the tables. The column names are set with a rule that puts an _ before each capital letter of a field name (hibernate.physical_naming_strategy=PhysicalNamingStrategySnakeCaseImpl). This is the same class that the spring-boot-hibernate jar of Spring Boot 4.1.1 points to as its default naming rule (this was checked by the class name inside the jar, and Spring Boot was not actually started), and what the column name becomes without this setting was not checked. What was checked against the run is whether JPA fails while starting and the kind and first line of the message of the exception, the tables and columns in H2 right after the start and after the run (INFORMATION_SCHEMA.COLUMNS), the foreign key constraint (INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS and KEY_COLUMN_USAGE), the SQL caught with StatementInspector (the number and text of SELECT and INSERT, split into the saving step and the reading step), the text printed on the screen, and the rows of the orders table after the run. H2 stores table names in capital letters, but the screen writes them in lowercase, and the member table that the foreign key points to is written as Member, following the entity name. The order of the columns is as seen in H2 and is not the same as the order in which the fields are written (when buyer_id was set with @JoinColumn, buyer_id came before id). What the documentation defines is Section 2.11 of the Jakarta Persistence 3.2 specification (a field that shows an association gets one of @OneToOne, @OneToMany, @ManyToOne, and @ManyToMany), Section 2.12 (the default mapping of associations) and Section 2.12.3.2 (for a unidirectional ManyToOne, a foreign key column that points to the primary key of the other table is placed in the table of this entity, and the name of that column joins the field name, an underscore, and the name of the other side's primary key column), and the API documentation of ManyToOne and JoinColumn (the default naming rule when the name of JoinColumn is not written). The body of the specification has no separate description of the annotations, so these two were read from the API documentation. These documents were opened with WebFetch, and what came back was a summary, not the original text, so each sentence could not be checked against the original. The following is not defined by the documentation but is behavior observed on this version. If you remove @ManyToOne, JPA throws a MappingException when it starts (the first line of the message is Incorrect use of entity type 'app.Member' (possibly due to missing association mapping annotation), and the inner cause is that it could not decide the JdbcType that fits the Java type 'app.Member'), and not one table is created. The member table is not created either, so the row (1, Kim) that is put in ahead of time does not exist. When there is an association, the SELECT that reads the order again was 1, and it was a statement that read the member table together (left join). When only a number was kept, it was 2 in all: 1 SELECT that read the order and 1 SELECT of the Member find that we called. The details of when and how these SELECT statements are sent are covered in the labs on the study side. The text of a statement may differ by Hibernate version, so the screen says "In this run, the statement that Hibernate 7.4.5 made". The @Table that sets the table name to orders was added because order is a reserved word in SQL, and it is not the topic of this lab. Only the fields id and member (or memberId) were covered, and the opposite direction (a bidirectional association where Member has a list of orders), the owning side of an association, lazy loading, the case where the other side does not exist (optional), and cascade were not covered. The association in this lab is unidirectional: it points only from Order to Member. A check against execution on MySQL was not done.
Previous lab: Repositories · Next lab: Lazy fetching
Struck-through text was removed from the first version of the code; highlighted text was added.