@OneToOne Mapping: Uni-Directional

One-to-One mapping μ€ Uni-Directional μ κ²½μ°, μμ κ°μ΄ ꡬ쑰λ₯Ό 그릴 μ μλ€. μ’ μλ entity μ PK λ₯Ό μμ entity κ° FK λ‘ μ¬μ©νλ κ²μ΄λ€.
κ·Έλ λ€λ©΄ SQL μμλ μ΄λ»κ² FK λ₯Ό μ§μ ν΄μΌ νλ κ²μΌκΉ?
Defining Foreign Key
CREATE TABLE `instructor` (
...
CONSTRAINT `FK_DETAIL` FOREIGN KEY(`instructor_detail_id)
REFERENCES `instructor_detail` (`id`)
);CONSTRAINT FK_DETAIL μ μ μ½ μ‘°κ±΄μ μ΄λ¦μ λνλ΄λ κ²μ΄λ€. μ¬κΈ°μ FK_DETAIL μ΄λΌλ μ΄λ¦μ μ μ½ μ‘°κ±΄μ, instructor table μ instructor_detail_id column μ΄, instructor_detail table μ id column μ μ°Έμ‘°νλλ‘ κ°μ νλ μΈλ ν€ μ μ½ μ‘°κ±΄(Foreign Key Constraint) μ΄λ€.
More on Foreign Key
FK λ₯Ό μ¬μ©ν¨μ μμ΄μ κ°μ₯ μ£Όμν λͺ©μ μ Referential Integrity μ΄λ€. Referential Integrity λ Database μμ FK κ° κ°λ¦¬ν€λ κ°μ΄ μ€μ λ‘ μ‘΄μ¬ν΄μΌ νλ€λ κ·μΉμ΄λ€.
λν entity κ°μ κ΄κ³λ₯Ό destroy ν μ μλ method λ° operation λ±μ λνμ¬ λ³΄νΈν μ μμΌλ©°, μμμ μΈκΈνλλ‘ FK κ° νμ valid ν reference λ₯Ό κ°κ³ μλ€κ³ 보μ₯ν μ μκ² λλ€.
@OneToOne - Cascade Types
| Cascade Types | Description |
|---|---|
| PERSIST | entity κ° persisted/saved λΌλ©΄, related entity λ ν¨κ» persisted |
| REMOVE | entity κ° removed/deleted λΌλ©΄, realted entity λ ν¨κ» deleted |
| REFRESH | entity κ° refreshed λλ©΄, related entity λ ν¨κ» refreshed |
| DETACH | entity κ° detached λλ©΄, related entity λ ν¨κ» detached |
| MERGE | entity κ° merged λλ©΄, realted entity λ ν¨κ» merged |
| ALL | μμμ λͺ¨λ cascade types |
μλ Uni-Directional μ One-to-One mapping μμ cascade types λ₯Ό μ 리ν νμ΄λ€. default λ‘λ μ무 cascade option λ μ€νλμ§ μλλ€.
Configure Multiple Cascade Types
@OneToOne(casecade={CasecadeType.DETACH,
CasecadeType.MERGE,
CasecadeType.PERSIST,
CasecadeType.REFRESH,
CasecadeType.REMOVE,})Development Process
Uni-Directional μ One-to-One mapping μ λν development process λ λ€μκ³Ό κ°λ€.
- Prep Work - Define database tables
- Create InstructorDetail class
- Create Instrcutor class
- Create Main App
Step 1: Prep Work - Define database tables
λ¨Όμ DB table λΆν° μ μνμ.
DROP SCHEMA IF EXISTS `hb-01-one-to-one-uni`;
CREATE SCHEMA `hb-01-one-to-one-uni`;
USE `hb-01-one-to-one-uni`;
SET FOREIGN_KEY_CHECKS = 0;μ°μ schema λ₯Ό μ μνμ. μμ κ°μ΄ κΈ°μ‘΄μ μ‘΄μ¬νλμ§ κ²μ¬νκ³ , μλ‘ μμ±ν λ€ ν΄λΉ schema λ₯Ό μ¬μ©νλ€.
λ§μ§λ§ μ€μ μ‘΄μ¬νλ SET FOREIGN_KEY_CHECKS = 0; λ μΈλ ν€ κ²μ¬, μμΈνκ²λ μ°Έμ‘° λ¬΄κ²°μ± κ²μ¬λ₯Ό λΉνμ±ννλ€λ λͺ
λ Ήμ΄λ€. λΉμ°νκ²λ νμ±νλ = 1 λ‘ μ€μ νλ©΄ λλ€. ν΄λΉ κ²μ¬λ μ μ°λ κ±ΈκΉ?
Referentail Integrity, μ¦ μ°Έμ‘° λ¬΄κ²°μ± κ²μ¬λ λΆλͺ¨ table κ³Ό μμ table μ μ°Έμ‘° κ΄κ³μ λν 무결μ±μ κ²μ¬νλ κ²μ΄λ€. μ°Έμ‘° 무결μ±μ΄ μ€μ λμ΄ μλ κ²½μ°, λΆλͺ¨ table μ μμ ν λλ μμ table μ λ°μ΄ν°λ₯Ό λ£μ λ μ μ½μ΄ λ°μνλ€.
λ§μΉ C/C++ μμ Dangling Pointer Problem κ³Ό λΉμ·ν μν©μ κ²μ¬νλ κ²μ΄λΌκ³ λ λ³Ό μ μμ κ² κ°λ€.
ν΄λΉ μ€μ μ μ²μ schema μ table λ€μ define ν λλ§ μ¬μ©νλ κ²μ΄ μ’κ³ , μ€μ λ‘ ν μ€νΈ λ¨κ³λ μ΄μ λ¨κ³μμλ λ€μ νμ±νλ κ²μ΄ λ°λμ§νλ€.
κ·Έλ¦¬κ³ μμ table μ ν΄λΉνλ instructor_detail table μ define νμ.
CREATE TABLE `instructor_detail` (
`id` int NOT NULL AUTO_INCREMENT,
`youtube_channel` varchar(128) DEFAULT NULL,
`hobby` varchar(45) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1;primary key λ id λ‘ μ€μ νκ³ κ·Έ μ΄μΈμλ νΉλ³ν μ€μ ν΄μ€ κ²μ μλ€. μ΄μ λΆλͺ¨ table μ ν΄λΉνλ instuctor table μ define νμ.
CREATE TABLE `instructor` (
`id` int NOT NULL AUTO_INCREMENT,
`first_name` varchar(45) DEFAULT NULL,
`last_name` varchar(45) DEFAULT NULL,
`email` varchar(45) DEFAULT NULL,
`instructor_detail_id` int DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `FK_DETAIL_idx` (`instructor_detail_id`),
CONSTRAINT `FK_DETAIL` FOREIGN KEY (`instructor_detail_id`) REFERENCES `instructor_detail` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1;foreign key λ‘ μ¬μ©ν instructor_detail_id column μ define νκ³ , id λ₯Ό primary key λ‘ μ€μ νλ€.
κ·Έλ¦¬κ³ , instructor_detail_id column μ λνμ¬ FK_DETAIL_idx λΌλ μ΄λ¦μ index λ₯Ό μμ±νλ€. index λ κ²μ μλλ₯Ό ν₯μμν€λ μ₯μ μ΄ μ‘΄μ¬νλ€. νμ§λ§ ν΄λΉ λΆλΆμ explicit νκ² μμ±νμ§ μμλ MySQL μ μλμΌλ‘ μ¨κ²¨μ§ index λ₯Ό μμ±νμ§λ§, index μ λν μ΄λ¦μ μ¬μ©μκ° μ§μ ν μ μκ³ λ΄λΆμ μΌλ‘ instructor_ibfk_1 λ±μ μ΄λ¦μΌλ‘ μ μ₯λλ€.
κ·Έ λ€μ FK_DETAIL μ΄λΌλ μ΄λ¦μΌλ‘ μ μ½ μ‘°κ±΄μ μ€μ νλλ°, λ table κ°μ κ΄κ³λ₯Ό κ°μ λ‘ μ°κ²°ν΄μ€λ€. instructor_detail_id column μ FK λ‘ μ¬μ©νκ³ , instructor_detail table μ id column μ μ°Έμ‘°νλλ‘ νλ€. λ§μ§λ§μΌλ‘, λΆλͺ¨ table μμ ν΄λΉ λ°μ΄ν°λ₯Ό μμ ν μ μκ³ , κ°μ λ³κ²½ν μ μκ² μ€μ νλ€.
SET FOREIGN_KEY_CHECKS = 1;λ§μ§λ§μΌλ‘ Referentail Integrity λ₯Ό λ€μ νμ±νμν¨λ€.
μ΄ν MySQL Workbench μμ [Database] > [Reverse Engineer] λ©λ΄λ₯Ό ν΅νμ¬ Diagram μ€μ μ ν λ€μ λ€μκ³Ό κ°μ΄ Cardinality λ₯Ό One-to-One μΌλ‘ λ°κΎΈμ΄μ£Όλ©΄ λλ€.
μ diagram μ λ³΄κ³ μ μ μλ―μ΄, instructor_detail table μ PK λ‘ μ¬μ©λλ id column μ instructor table μ FK μ μνμ¬ μ°Έμ‘°λλ―λ‘, instructor_detail table μ parent table λ‘, instructor table μ child table λ‘ λΆλ₯ν μ μλ€.
λ§μΉ class μ μμκ³Ό κ°μ κ°λ μ²λΌ, μ°Έμ‘°λλ μͺ½μ΄ parent μ΄κ³ μ°Έμ‘°νλ μͺ½μ΄ child μ΄λ€.
Step 2: Create InstructorDetail class
@Entity
@Table(name="instructor_detail")
public class InstructorDetail {
// define the fields ...
// define constructors ...
...
}instructor_detail table μμ μ€μ ν column λλ‘ μλ§κ² InstructorDetail entity class λ₯Ό define ν΄μ€λ€.
Step 3: Create Instrcutor class
@Entity
@Table(name = "instructor")
public class Instructor {
// define fields ...
// set up mapping to InstructorDetail entity
@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "instructor_detail_id")
private InstructorDetail instructorDetail;
...
}Uni-Directional One-to-One mapping μ΄λ―λ‘ child table μͺ½μμλ§ mapping κ΄κ³λ₯Ό define ν΄μ£Όλ©΄ λλ€.
κ·Έ λ°©λ²μ μμ κ°μ΄ @OneToOne annotation μ ν΅νμ¬ cascade option κ³Ό ν¨κ» define νλ©΄ λκ³ , @JoinColumn annotation μ ν΅νμ¬ FK λ‘ μ¬μ©ν column μ΄λ¦μ μ§μ νλ€. ν΄λΉ column μΌλ‘ λ κ°μ table μ΄ μ°κ²°λλ κ²μ΄λ€.
Step 4: Create Main App
Main App μ μμ±νκΈ° μ μ Entity μ κ·Όμ λμ ν DAO λ₯Ό λ¨Όμ define νλ κ²μ΄ λ λμ κ² κ°λ€.
public interface AppDAO {
void save(Instructor instructor);
}@Repository
public class AppDAOImpl implements AppDAO {
// define field for entity manager
private EntityManager entityManager;
// inject entity manager using constructor injection
public AppDAOImpl(EntityManager entityManager) {
this.entityManager = entityManager;
}
@Override
@Transactional public void save(Instructor instructor) {
entityManager.persist(instructor);
}
}DAO μ λν interface μ, κ·Έμ λν implementation μ μμ κ°μ΄ define ν΄μ€λ€. implementation class μμλ, μ¬κΈ°μ μ μ μλ―μ΄, @Repository annotation μ ν΅νμ¬ ν΄λΉ class μ λν object κ° Spring Bean μΌλ‘ μλ λ±λ‘λ μ μλλ‘ νλ€.
μ΄μ Main App μΌλ‘ λμ΄κ°μ μ€μ λ°μ΄ν°λ₯Ό μ¬μ©νμ¬ test ν΄λ³΄μ.
@SpringBootApplication
public class CruddemoApplication {
public static void main(String[] args) {
SpringApplication.run(CruddemoApplication.class, args);
}
@Bean
public CommandLineRunner commandLineRunner(AppDAO appDAO) {
return runner -> {
createInstructor(appDAO);
};
}
private void createInstructor(AppDAO appDAO) {
// create the instructor
// create the instructor detail
// associate the objects
tempInstructor.setInstructorDetail(tempInstructorDetail);
// save the instructor
appDAO.save(tempInstructor);
...
}
}μμ κ°μ΄ CommandLineRunner λ₯Ό ν΅νμ¬ Application μ΄ μ€νλ μ§νμ μ€νν code λ₯Ό μ€μ ν μ μλ€. createInstructor() method λ₯Ό μμ κ°μ μμλ‘ define ν λ€μ ν΄λΉ method λ₯Ό μ€ννλ μμΌλ‘ ꡬμ±νλ©΄ λλ€.
Application μ μ€ννκΈ° μ΄μ μ application.properties λ₯Ό ν΅νμ¬ Hibernate μ logging κΈ°λ₯μ μ¬μ©νμ.
logging.level.org.hibernate.SQL=trace
logging.level.org.hibernate.orm.jdbc.bind=traceμμ κ°μ΄ μ€μ ν¨μΌλ‘μ¨ μ€μ DB μμ μ¬μ©λλ SQL query μ ν΄λΉ query μ μ¬μ©λλ μ€μ data λ€μ΄ μ΄λ€ κ²μΈμ§λ₯Ό logging ν μ μλ€.
μ΄μ Application μ μ€νν΄λ³΄μ.
One-to-One Mapping Uni-Direction - Test (save)

console μ μΆλ €λ log μμ λ³Ό μ μλ―μ΄, μ€μ ν΄μ€ properties μ μνμ¬ μΆλ ₯λ SQL query μ μ΄μ binding λ value λ€μ νμΈν μ μλ€.


κ²°κ³Όμ μΌλ‘ μ€μ DB table μλ data λ€μ΄ μ μμΈ κ²μ λ³Ό μ μλ€. Main App μμ λ¨μν Instructor object λ§ μ μ₯νλλ°λ InstructorDetail object κ° κ°μ΄ μ μ₯λ κ²μ λ³Ό μ μλ€.
One-to-One Mapping Uni-Direction - Test (find)
μ΄μ μ μ₯λ data λ₯Ό κΈ°λ°μΌλ‘, instuctor λ₯Ό μ‘°ννκ³ μ΄λ₯Ό ν΅νμ¬ instructor detail κΉμ§λ μ‘°νν μ μλμ§ test ν΄λ³΄μ.
μ°μ AppDAO interface μ findInstructorById λ₯Ό μΆκ°νκ³ μλμ κ°μ΄ AppDAOImpl μμ overriding νμ.
@Override
public Instructor findInstructorById(int theId) {
return entityManager.find(Instructor.class, theId);
}κ·Έλ¦¬κ³ Main App μμλ μλμ κ°μ΄ findInstructor method λ₯Ό μΆκ°νμ¬ test λ₯Ό μ§ννμ.
private void findInstructor(AppDAO appDAO) {
int theId = 1;
System.out.println("Finding instructor id: " + theId);
Instructor tempInstructor = appDAO.findInstructorById(theId);
System.out.println("tempInstructor: " + tempInstructor);
System.out.println("the associate instructorDetail only: " + tempInstructor.getInstructorDetail());
}μ΄μ Application μ μ€ννμ¬ console log λ₯Ό νμΈν΄λ³΄λ©΄,

μμ κ°μ΄ instructor μ κ·Έμ detail κΉμ§ λͺ¨λ μ μ‘°νν κ²μ μ μ μλ€.
One-to-One Mapping Uni-Direction - Test (delete)
μ΄λ²μλ instructor λ₯Ό μμ νμ λ detail λ κ°μ΄ μμ λλμ§ test ν΄λ³΄μ.
@Override
@Transactional
public void deleteInstructorById(int theId) {
Instructor tempInstructor = entityManager.find(Instructor.class, theId);
entityManager.remove(tempInstructor);
}μμ κ°μ΄ AppDAOImpl μ method λ₯Ό μΆκ°ν΄μ£Όκ³ , Main App μ λ€μκ³Ό κ°μ΄ μΆκ°νλ€.
private void deleteInstructor(AppDAO appDAO) {
int theId = 1;
System.out.println("Deleting instructor id: " + theId);
appDAO.deleteInstructorById(theId);
System.out.println("Done! :)");
}Main App μμλ DAO λ₯Ό inject λ°μ μ¬μ©νκΈ° λλ¬Έμ μ§μ μ μΌλ‘ Database μ μ κ·Όνμ§ μκ³ DAO λ₯Ό ν΅νμ¬ μμ½κ² DB μ μ κ·Όν μ μλ€.

μ΄ν test λ₯Ό ν΄λ³΄λ©΄ μμ κ°μ΄ instructor object λ₯Ό λ¨Όμ delete ν μ΄ν, detail object κ° delete λ κ²μ λ³Ό μ μλ€.
μ΄λ κ² parent table κ³Ό child table μ λν save, find, delete μμ μ μμλ΄€λ€.
μμμ λ€λ£¬ λ€μν CasecadeType option μ ν΅νμ¬ μμμ± μ μ΄, μ¦ cascading persistence μ λλ₯Ό μ‘°μ ν μ μλ€.