Spring - Hibernate: асоціація один до багатьох

Продовжуємо цикл статей — переказів за Spring і Hibernate, krams .

Попередня стаття:
Spring MVC 3, Анотації Hibernate, MySQL. Туторіал по інтеграції

Введення.
У цьому уроці ми познайомимося з використанням відносини один-до-багатьох, використовуючи анотації Hibernate і Spring MVC 3. Ми будемо використовувати аннотоцию @OneToMany для вказівки відносин між нашими об'єктами. Ми не будемо використовувати каскадні типи або fetch-стратегії, замість цього ми скористаємося стандартними налаштуваннями @OneToMany.

Що таке асоціація один-ко-багатьом?
Асоціація один-до-багатьох виникає тоді, коли кожного запису в таблиці А, відповідає безліч записів у таблиці Б, але кожен запис у таблиці Б має лише одну відповідний запис в таблиці А.

Специфікація нашої програми.

Додаток є простий CRUD системою управління списком записів. Кожен запис відповідає одній особі, вона містить персональні дані та дані кредитної картки. Кожна особа може володіти кількома картками. Так само ми додамо систему редагування осіб і кредиток.

Нижче наведені скріншоти з майбутнього додатки:








Доменні об'єкти

Грунтуючись на специфікації, ми маємо два доменних об'єкта: персона (Person) і кредитка (Credit Card).

У об'єкта персони повинні бути такі поля:
— id
— first name (ім'я)
— last name (прізвище)
— money (гроші)
— credit cards (кредитки)

У об'єкта кредитка наступні поля:
— id
— type (тип)
— number (номер)

Зверніть увагу, що кожної персони відповідає безліч кредитних карт, а слідчо ми використовуємо асоціацію один-до-багатьох. Звичайно, ми можемо подивитися на цю ситуацію з іншого боку і використовувати асоціацію багато-до-одного, але це буде темою іншого уроку.
Розробка
Ми розіб'ємо нашу розробку на три шари: доменний, сервісний і контролер, потім вкажемо конфігураційні файли.

Почнемо з доменного шару.
Як говорилося раніше, у нас є два доменних об'єкта: Person і CreditCard. Отже ми оголосимо два POJO об'єкта, що представляють наш доменний шар. У кожного з них буде анотація Посилання для зберігання в базі даних.

Person.java
package org.krams.підручник.domain;

import java.io.Serializable;
import java.util.Set;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Table;

/**
* Represents a person entity
*
* @author Krams at {@link http://krams915@blogspot.com}
*/
@Entity
@Table(name = "PERSON")
public class Person implements Serializable {

private static final long serialVersionUID = -5527566248002296042L;

@Id
@Column(name = "ID")
@GeneratedValue
private id Integer;

@Column(name = "FIRST_NAME")
private String firstName;

@Column(name = "LAST_NAME")
private String lastName;

@Column(name = "MONEY")
private Double money;

@OneToMany
private Set<CreditCard> creditCards;

public Integer getId() {
return id;
}

public void setId(id Integer) {
this.id = id;
}

public String getFirstName() {
return firstName;
}

public void setFirstName(String firstName) {
this.firstName = firstName;
}

public String getLastName() {
return lastName;
}

public void setLastName(String lastName) {
this.lastName = lastName;
}

public Double getMoney() {
return money;
}

public void setMoney(Double money) {
this.money = money;
}

public Set<CreditCard> getCreditCards() {
return creditCards;
}

public void setCreditCards(Set<CreditCard> creditCards) {
this.creditCards = creditCards;
}
}



Клас Person відображено в таблиці PERSON. І виглядає наступним чином:

Зверніть увагу на анотацію @OneToMany для змінної creditCards, ми не вказали ні каскадний тип, ні fetch-стратегію, покладаючись на налаштування за замовчуванням. Пізніше ми дізнаємося деякі проблеми, пов'язані з цим.
Person.java
@Entity
@Table(name = "PERSON")
public class Person implements Serializable {

...

@OneToMany
private Set<CreditCard> creditCards;

...
}


CreditCard.java
package org.krams.підручник.domain;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;

/**
* Represents a credit card entity
*
* @author Krams at {@link http://krams915@blogspot.com}
*/
@Entity
@Table(name = "CREDIT_CARD")
public class CreditCard implements Serializable {

private static final long serialVersionUID = 5924361831551833717L;

@Id
@Column(name = "ID")
@GeneratedValue
private id Integer;

@Column(name = "TYPE")
private type String;

@Column(name = "NUMBER")
private String number;

public Integer getId() {
return id;
}

public void setId(id Integer) {
this.id = id;
}

public String getType() {
return type;
}

public void setType(String type) {
this.type = type;
}

public String getNumber() {
return number;
}

public void setNumber(String number) {
this.number = number;
}
}


Клас CreditCard відображено в таблиці CREDIT_CARD і виглядає:

Використовуючи phpmyadmin db designer подивимося на відносини між Person і CreditCard:

Поглянемо на згенеровані таблиці:


Ми вказували лише дві сутності: CreditCard та Person, і очікували побачити тільки дві таблиці в базі даних. Так чому ж їх три? Так як в налаштування за замовчуванням створюється третя єднальна таблиця.

Цитата з Hibernate Annotations Reference Guide:
Без опису відображення, для співвідношення один-до-багатьох використовується єднальна таблиця. Ім'ям таблиці є конкатенація імені першої таблиці, символу «_» та імені другої таблиці. Для забезпечення співвідношення один до багатьох колонці з id першої таблиці присвоюється модифікатор UNIQUE.
Пізніше ми обговоримо інші недоліки установок за замовчуванням.

Сервісний шар.
Після оголошення доменних об'єктів, нам необхідно створити сервісний шар, який містить два сервісу: PersonService і CreditCardService.

PersonService відповідає за обробку CRUD операцій над сутністю Person. Кожен метод в кінцевому підсумку передає об'єкт сесії Hibernate.

PersonService.java
package org.krams.підручник.service;

import java.util.List;
import java.util.Set;

import javax.annotation.Resource;

import org.apache.log4j.Logger;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.krams.підручник.domain.CreditCard;
import org.krams.підручник.domain.Person;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
* Service for processing Persons
*
* @author Krams at {@link http://krams915@blogspot.com
*/
@Service("personService")
@Transactional
public class PersonService {

protected static Logger logger = Logger.getLogger("service");

@Resource(name="sessionFactory")
private SessionFactory sessionFactory;

/**
* Retrieves all persons
*
* @return a list of persons
*/
public List<Person> getAll(){
logger.debug("Retrieving all persons");

// Retrieve session from Hibernate
Session session = sessionFactory.getCurrentSession();

// Create a Hibernate query (HQL)
Query query = session.createQuery("FROM Person");

// Retrieve all
return query.list();
}

/**
* Retrieves a single person
*/
public Person get( id Integer ) {
// Retrieve session from Hibernate
Session session = sessionFactory.getCurrentSession();

// Retrieve existing person
// Create a Hibernate query (HQL)
Query query = session.createQuery("FROM Person as p LEFT JOIN FETCH p.creditCards WHERE p.id="+id);

return (Person) query.uniqueResult();
}

/**
* Adds a new person
*/
public void add(Person person) {
logger.debug("Adding new person");

// Retrieve session from Hibernate
Session session = sessionFactory.getCurrentSession();

// Persists to db
session.save(person);
}

/**
* Deletes an existing person
* @param id the id of the existing person
*/
public void delete(id Integer) {
logger.debug("Deleting existing person");

// Retrieve session from Hibernate
Session session = sessionFactory.getCurrentSession();

// Create a Hibernate query (HQL)
Query query = session.createQuery("FROM Person as p LEFT JOIN FETCH p.creditCards WHERE p.id="+id);

// Retrieve record
Person person = (Person) query.uniqueResult();

Set<CreditCard> creditCards =person.getCreditCards();

// Delete person
session.delete(person);

// Delete associated credit cards
for (CreditCard creditCard: creditCards) {
session.delete(creditCard);
}
}

/**
* Edits an existing person
*/
public void edit(Person person) {
logger.debug("Editing existing person");

// Retrieve session from Hibernate
Session session = sessionFactory.getCurrentSession();

// Retrieve existing person via id
Person existingPerson = (Person) session.get(Person.class, person.getId());

// Assign updated values to this person
existingPerson.setFirstName(person.getFirstName());
existingPerson.setLastName(person.getLastName());
existingPerson.setMoney(person.getMoney());

// Save updates
session.save(existingPerson);
}
}



PersonService виглядає оманливо простим, він вирішує дві основні проблеми, які заслуговують особливої уваги:

Проблема 1. Fetch стратегії
Отримання запису особи не довантажує пов'язані з ним записи кредиток.

Наступний запит отримує персону по id.
Query query = session.createQuery(«Person FROM WHERE p.id=»+id);

Проблема відбувається тому, що ми не вказали fetch-стратегію коли описували анотацію @OneToMany, для того, що б це виправити — змінимо запит:
Query query = session.createQuery(«FROM Person as p LEFT JOIN FETCH p.creditCards WHERE p.id=»+id);

Проблема 2. Каскадні типи.
Видалення персони не призводить до видалення соответствуещих йому кредитних карт.
Наступна запис видаляє запис про особу:
session.delete(person);

Проблема виникає тому, що ми не вказали каскадні типи в анотації @OneToMany. Це означає, що ми повинні реалізувати свою стратегію видалення записів про кредитки.

Спочатку необхідно створити запит для отримання кредитних карт, які ми розміщуємо на тимчасове зберігання в колекцію. Потім ми видаляємо запис про персони. Після всього витягуючи з колекції, ми видаляємо кретки одну за одною.
Виправлений запит
// Create a Hibernate query (HQL)
Query query = session.createQuery("FROM Person as p LEFT JOIN FETCH p.creditCards WHERE p.id="+id);

// Retrieve record
Person person = (Person) query.uniqueResult();

Set<CreditCard> creditCards =person.getCreditCards();

// Delete person
session.delete(person);

// Delete associated credit cards
for (CreditCard creditCard: creditCards) {
session.delete(creditCard);
}



CreditCardService
CreditCardService відповідає за обробку CRUD операцій над сутністю CreditCard. Кожен метод в кінцевому підсумку передає об'єкт сесії Hibernate.
CreditCardService.java
package org.krams.підручник.service;

import java.util.ArrayList;
import java.util.List;
import javax.annotation.Resource;

import org.apache.log4j.Logger;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.krams.підручник.domain.CreditCard;
import org.krams.підручник.domain.Person;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
* Service for processing Credit Cards
*
* @author Krams at {@link http://krams915@blogspot.com
*/
@Service("creditCardService")
@Transactional
public class CreditCardService {

protected static Logger logger = Logger.getLogger("service");

@Resource(name="sessionFactory")
private SessionFactory sessionFactory;

/**
* Retrieves all credit cards
*/
public List<CreditCard> getAll(Integer personId) {
logger.debug("Retrieving all credit cards");

// Retrieve session from Hibernate
Session session = sessionFactory.getCurrentSession();

// Create a Hibernate query (HQL)
Query query = session.createQuery("FROM Person as p LEFT JOIN FETCH p.creditCards WHERE p.id="+personId);

Person person = (Person) query.uniqueResult();

// Retrieve all
return new ArrayList<CreditCard>(person.getCreditCards());
}

/**
* Retrieves all credit cards
*/
public List<CreditCard> getAll() {
logger.debug("Retrieving all credit cards");

// Retrieve session from Hibernate
Session session = sessionFactory.getCurrentSession();

// Create a Hibernate query (HQL)
Query query = session.createQuery("FROM CreditCard");

// Retrieve all
return query.list();
}

/**
* Retrieves a single credit card
*/
public CreditCard get( id Integer ) {
// Retrieve session from Hibernate
Session session = sessionFactory.getCurrentSession();

// Retrieve existing credit card
CreditCard creditCard = (CreditCard) session.get(CreditCard.class id);

// Persists to db
return creditCard;
}

/**
* Adds a new credit card
*/
public void add(Integer personId, CreditCard creditCard) {
logger.debug("Adding new credit card");

// Retrieve session from Hibernate
Session session = sessionFactory.getCurrentSession();

// Persists to db
session.save(creditCard);

// Add to person as well
// Retrieve existing person via id
Person existingPerson = (Person) session.get(Person.class, personId);

// Assign updated values to this person
existingPerson.getCreditCards().add(creditCard);

// Save updates
session.save(existingPerson);
}

/**
* Deletes an existing credit card
*/
public void delete(id Integer) {
logger.debug("Deleting existing credit card");

// Retrieve session from Hibernate
Session session = sessionFactory.getCurrentSession();

// Delete reference to foreign key credit card first
// We need a SQL query instead of HQL query here to access the third table
Query query = session.createSQLQuery("DELETE FROM PERSON_CREDIT_CARD " +
"WHERE creditCards_ID="+id);

query.executeUpdate();

// Retrieve existing credit card
CreditCard creditCard = (CreditCard) session.get(CreditCard.class id);

// Delete
session.delete(creditCard);
}

/**
* Edits an existing credit card
*/
public void edit(CreditCard creditCard) {
logger.debug("Editing existing creditCard");

// Retrieve session from Hibernate
Session session = sessionFactory.getCurrentSession();

// Retrieve existing credit card id via
CreditCard existingCreditCard = (CreditCard) session.get(CreditCard.class, creditCard.getId());

// Assign updated values to this credit card
existingCreditCard.setNumber(creditCard.getNumber());
existingCreditCard.setType(creditCard.getType());

// Save updates
session.save(existingCreditCard);
}
}


Звернемо особливу увагу на метод delete():
delete()
public void delete(id Integer) {
logger.debug("Deleting existing credit card");

// Retrieve session from Hibernate
Session session = sessionFactory.getCurrentSession();

// Delete reference to foreign key credit card first
// We need a SQL query instead of HQL query here to access the third table
Query query = session.createSQLQuery("DELETE FROM PERSON_CREDIT_CARD " +
"WHERE creditCards_ID="+id);

query.executeUpdate();

// Retrieve existing credit card
CreditCard creditCard = (CreditCard) session.get(CreditCard.class id);

// Delete
session.delete(creditCard);
}



Для видалення кредтки, спочатку необхідно видалити її зі сполучною таблиці, наступним запитом:
Query query = session.createSQLQuery(«DELETE FROM PERSON_CREDIT_CARD » +
«WHERE creditCards_ID=»+id);


Зверніть увагу, що єднальна таблиця створена Hibernate і запит SQL, а не HQL.
Після видалення даних з сполучною таблиці, видаляємо інформацію з таблиці CREDIT_CARD.
session.delete(creditCard)

Шар контролера.
Після створення сервісного і доменного шарів, необхідно створити шар контролер. Ми створимо два контролера: MainController і CreditCardController.
MainController

MainController відповідає за обробку запитів до записів осіб. Кожен CRUD запит в кінцевому рахунку передається на PersonService, а потім повертає відповідну JSP сторінки.

MainController.java
package org.krams.підручник.controller;

import java.util.ArrayList;
import java.util.List;

import javax.annotation.Resource;

import org.apache.log4j.Logger;
import org.krams.підручник.domain.Person;
import org.krams.підручник.dto.PersonDTO;
import org.krams.підручник.service.CreditCardService;
import org.krams.підручник.service.PersonService;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

/**
* Handles person request
*
* @author Krams at {@link http://krams915@blogspot.com
*/
@Controller
@RequestMapping("/main/record")
public class MainController {

protected static Logger logger = Logger.getLogger("controller");

@Resource(name="personService")
private PersonService personService;

@Resource(name="creditCardService")
private CreditCardService creditCardService;

/**
* Retrieves the "Records" page
*/
@RequestMapping(value = "/list", method = RequestMethod.GET)
public String getRecords(Model model) {
logger.debug("Received request to show records page");

// Retrieve all persons
List<Person> persons = personService.getAll();

// Prepare object model
List<PersonDTO> personsDTO = new ArrayList<PersonDTO>();

for (Person person: persons) {
// Create new data transfer object
PersonDTO dto = new PersonDTO();

dto.setId(person.getId());
dto.setFirstName(person.getFirstName());
dto.setLastName(person.getLastName());
dto.setMoney(person.getMoney());
dto.setCreditCards(creditCardService.getAll(person.getId()));

// Add to model list
personsDTO.add(dto);
}

// Add to model
model.addAttribute("persons", personsDTO);

// This will to resolve /WEB-INF/jsp/records.jsp
return "records";
}

/**
* Retrieves the "Add New Record" page
*/
@RequestMapping(value = "/add", method = RequestMethod.GET)
public String getAdd(Model model) {
logger.debug("Received request to show add page");

// Create new Person and add to model
model.addAttribute("personAttribute", new Person());

// This will to resolve /WEB-INF/jsp/add-record.jsp
return "add-record";
}

/**
* Adds a new record
*/
@RequestMapping(value = "/add", method = RequestMethod.POST)
public String postAdd(@ModelAttribute("personAttribute") Person person) {
logger.debug("Received request to add new record");

// Delegate to service
personService.add(person);

// Redirect to url
return "redirect:/krams/main/record/list";
}

/**
* Deletes a record including all the associated credit cards
*/
@RequestMapping(value = "/delete", method = RequestMethod.GET)
public String getDelete(@RequestParam("id") Integer personId) {
logger.debug("Received request to delete record");

// Delete person
personService.delete(personId);

// Redirect to url
return "redirect:/krams/main/record/list";
}

/**
* Retrieves the "Edit Existing Record" page
*/
@RequestMapping(value = "/edit", method = RequestMethod.GET)
public String getEdit(@RequestParam("id") Integer personId, Model model) {
logger.debug("Received request to show edit page");

// Retrieve person by id
Person existingPerson = personService.get(personId);

// Add to model
model.addAttribute("personAttribute", existingPerson);

// This will to resolve /WEB-INF/jsp/edit-record.jsp
return "edit-record";
}

/**
* Edits an existing record
*/
@RequestMapping(value = "/edit", method = RequestMethod.POST)
public String postEdit(@RequestParam("id") Integer personId,
@ModelAttribute("personAttribute") Person person) {
logger.debug("Received request to edit existing person");

// Assign id
person.setId(personId);

// Delegate to service
personService.edit(person);

// Redirect to url
return "redirect:/krams/main/record/list";
}

}



Зверніть увагу, що метод getRecords() метод відображає Person і CreditCard як об'єкт передачі даних: PersonDTO.
getRecords()
/**
* Retrieves the "Records" page
*/
@RequestMapping(value = "/list", method = RequestMethod.GET)
public String getRecords(Model model) {
logger.debug("Received request to show records page");

// Retrieve all persons
List<Person> persons = personService.getAll();

// Prepare object model
List<PersonDTO> personsDTO = new ArrayList<PersonDTO>();

for (Person person: persons) {
// Create new data transfer object
PersonDTO dto = new PersonDTO();

dto.setId(person.getId());
dto.setFirstName(person.getFirstName());
dto.setLastName(person.getLastName());
dto.setMoney(person.getMoney());
dto.setCreditCards(creditCardService.getAll(person.getId()));

// Add to model list
personsDTO.add(dto);
}

// Add to model
model.addAttribute("persons", personsDTO);

// This will to resolve /WEB-INF/jsp/records.jsp
return "records";
}



PersonDTO виступає в ролі моделі даних для records.jsp.
PersonDTO.java
package org.krams.підручник.dto;

import java.util.List;

import org.krams.підручник.domain.CreditCard;

/**
* Data Transfer Object for displaying purposes
*/
public class PersonDTO {

private id Integer;
private String firstName;
private String lastName;
private Double money;
private List<CreditCard> creditCards;

public Integer getId() {
return id;
}
public void setId(id Integer) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public Double getMoney() {
return money;
}
public void setMoney(Double money) {
this.money = money;
}
public List<CreditCard> getCreditCards() {
return creditCards;
}
public void setCreditCards(List<CreditCard> creditCards) {
this.creditCards = creditCards;
}
}



CreditCardController
CreditCardController відповідає за обробку запитів для кредиток. Ми не будемо використовувати всі наявні в цьому контролері методи, вони були додані для повноти.

CreditCardController.java
package org.krams.підручник.controller;

import javax.annotation.Resource;

import org.apache.log4j.Logger;
import org.krams.підручник.domain.CreditCard;
import org.krams.підручник.service.CreditCardService;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

/**
* Handles credit card requests
*
* @author Krams at {@link http://krams915@blogspot.com
*/
@Controller
@RequestMapping("/main/creditcard")
public class CreditCardController {

protected static Logger logger = Logger.getLogger("controller");

@Resource(name="creditCardService")
private CreditCardService creditCardService;

/**
* Retrieves the "Add New Credit Card" page
*/
@RequestMapping(value = "/add", method = RequestMethod.GET)
public String getAdd(@RequestParam("id") Integer personId, Model model) {
logger.debug("Received request to show add page");

// Prepare object model
CreditCard creditCard = new CreditCard();

// Add to model
model.addAttribute("personId", personId);
model.addAttribute("creditCardAttribute", creditCard);

// This will to resolve /WEB-INF/jsp/add-credit-card.jsp
return "add-credit-card";
}

/**
* Adds a new credit card
*/
@RequestMapping(value = "/add", method = RequestMethod.POST)
public String postAdd(@RequestParam("id") Integer personId,
@ModelAttribute("creditCardAttribute") CreditCard creditCard) {
logger.debug("Received request to add new credit card");

// Delegate to service
creditCardService.add(personId, creditCard);

// Redirect to url
return "redirect:/krams/main/record/list";
}


/**
* Deletes a credit card
*/
@RequestMapping(value = "/delete", method = RequestMethod.GET)
public String getDelete(@RequestParam("id") Integer creditCardId) {
logger.debug("Received request to delete credit card");


// Delegate to service
creditCardService.delete(creditCardId);

// Redirect to url
return "redirect:/krams/main/record/list";
}

/**
* Retrieves the "Edit Existing Credit Card" page
*/
@RequestMapping(value = "/edit", method = RequestMethod.GET)
public String getEdit(@RequestParam("pid") Integer personId,
@RequestParam("cid") Integer creditCardId, Model model) {
logger.debug("Received request to show edit page");

// Retrieve credit card id by
CreditCard existingCreditCard = creditCardService.get(creditCardId);

// Add to model
model.addAttribute("personId", personId);
model.addAttribute("creditCardAttribute", existingCreditCard);

// This will to resolve /WEB-INF/jsp/edit-credit-card.jsp
return "edit-credit-card";
}

/**
* Edits an existing credit card
*/
@RequestMapping(value = "/edit", method = RequestMethod.POST)
public String postEdit(@RequestParam("id") Integer creditCardId,
@ModelAttribute("creditCardAttribute") CreditCard creditCard) {
logger.debug("Received request to add new credit card");

// Assign id
creditCard.setId(creditCardId);

// Delegate to service
creditCardService.edit(creditCard);

// Redirect to url
return "redirect:/krams/main/record/list";
}
}



VIEW шар.

Після обговорення доменного, сервісного і шару контролера, створимо шар VIEW. Він складається в основному з JSP сторінок. Ось вони:

records.jsp
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="с" %>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
< meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>Records</h1>

<c:url var="editImgUrl" value="/resources/img/edit.png" />
<c:url var="deleteImgUrl" value="/resources/img/delete.png" />
<c:url var="addUrl" value="/krams/main/record/add" />
<table style="border: 1px solid; width: 100%; text-align:center">
<thead style="background:#d3dce3">
<tr>
<th>Id</th>
<th>First Name</th>
<th>Last Name</th>
<th>Money</th>
<th colspan="2"></th>
<th>CC Type</th>
<th>CC Number</th>
<th colspan="3"></th>
</tr>
</thead>
<tbody style="background:#ccc">
<c:forEach items="${persons}" var="person">
<c:url var="editUrl" value="/krams/main/record/edit?id=${person.id}" />
<c:url var="deleteUrl" value="/krams/main/record/delete?id=${person.id}" />

<c:if test="${!empty person.creditCards}">
<c:forEach items="${person.creditCards}" var="creditCard">
<tr>
<td><c:out value="${person.id}" /></td>
<td><c:out value="${person.firstName}" /></td>
<td><c:out value="${person.lastName}" /></td>
<td><c:out value="${person.money}" /></td>
<td><a href="${editUrl}"><img src="${editImgUrl}"></img></a></td>
<td><a href="${deleteUrl}"><img src="${deleteImgUrl}"></img></a></td>

<td><c:out value="${creditCard.type}" /></td>
<td><c:out value="${creditCard.number}" /></td>
<c:url var="addCcUrl" value="/krams/main/creditcard/add?id=${person.id}" />
<c:url var="editCcUrl" value="/krams/main/creditcard/edit?pid=${person.id}&cid=${creditCard.id}" />
<c:url var="deleteCcUrl" value="/krams/main/creditcard/delete?id=${creditCard.id}" />
<td><a href="${addCcUrl}">+</a></td>
<td><a href="${editCcUrl}"><img src="${editImgUrl}"></img></a></td>
<td><a href="${deleteCcUrl}"><img src="${deleteImgUrl}"></img></a></td>
</tr>
</c:forEach>
</c:if>

<c:if test="${empty person.creditCards}">
<tr>
<td><c:out value="${person.id}" /></td>
<td><c:out value="${person.firstName}" /></td>
<td><c:out value="${person.lastName}" /></td>
<td><c:out value="${person.money}" /></td>
<td><a href="${editUrl}"><img src="${editImgUrl}"></img></a></td>
<td><a href="${deleteUrl}"><img src="${deleteImgUrl}"></img></a></td>

<td>N/A</td>
<td>N/A</td>
<c:url var="addCcUrl" value="/krams/main/creditcard/add?id=${person.id}" />
<td><a href="${addCcUrl}">+</a></td>
<td></td>
<td></td>
</tr>
</c:if>

</c:forEach>
</tbody>
</table>

<c:if test="${empty persons}">
No records found.
</c:if>

<p><a href="${addUrl}">Create new record</a></p>

</body>
</html>



Додати новий запис

add-record.jsp
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="с" %>
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
< meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>

<h1>Create New Record</h1>

<c:url var="saveUrl" value="/krams/main/record/add" />
<form:form modelAttribute="personAttribute" method="POST" action="${saveUrl}">
<table>
<tr>
<td><form:label path="firstName">First Name:</form:label></td>
<td><form:input path="firstName"/></td>
</tr>

<tr>
<td><form:label path="lastName">Last Name</form:label></td>
<td><form:input path="lastName"/></td>
</tr>

<tr>
<td><form:label path="money">Money</form:label></td>
<td><form:input path="money"/></td>
</tr>
</table>

<input type="submit" value="Save" />
</form:form>

</body>
</html>



Редагування існуючого запису:

edit-record.jsp
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="с" %>
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
< meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>

<h1>Edit Existing Record</h1>

<c:url var="saveUrl" value="/krams/main/record/edit?id=${personAttribute.id}" />
<form:form modelAttribute="personAttribute" method="POST" action="${saveUrl}">
<table>
<tr>
<td><form:label path="id">Id:</form:label></td>
<td><form:input path="id" disabled="true"/></td>
</tr>

<tr>
<td><form:label path="firstName">First Name:</form:label></td>
<td><form:input path="firstName"/></td>
</tr>

<tr>
<td><form:label path="lastName">Last Name</form:label></td>
<td><form:input path="lastName"/></td>
</tr>

<tr>
<td><form:label path="money">Money</form:label></td>
<td><form:input path="money"/></td>
</tr>
</table>

<input type="submit" value="Save" />
</form:form>

</body>
</html>



Додати нову кредитку:

add-credit-card.jsp
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="с" %>
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
< meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>

<h1>Add New Credit Card</h1>

<c:url var="saveUrl" value="/krams/main/creditcard/add?id=${personId}" />
<form:form modelAttribute="creditCardAttribute" method="POST" action="${saveUrl}">
<table>

<tr>
<td>Person Id:</td>
<td><input type="text" value="${personId}" disabled="true"/>
</tr>

<tr>
<td><form:label path="type">Type:</form:label></td>
<td><form:input path="type"/></td>
</tr>

<tr>
<td><form:label path="number">Number:</form:label></td>
<td><form:input path="number"/></td>
</tr>
</table>

<input type="submit" value="Save" />
</form:form>

</body>
</html>



Правити існуючу кредитку:

edit-credit-card.jsp
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="с" %>
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
< meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>

<h1>Edit Existing Credit Card</h1>

<c:url var="saveUrl" value="/krams/main/creditcard/edit?id=${creditCardAttribute.id}" />
<form:form modelAttribute="creditCardAttribute" method="POST" action="${saveUrl}">
<table>

<tr>
<td>Person Id:</td>
<td><input type="text" value="${personId}" disabled="true"/>
</tr>

<tr>
<td><form:label path="type">Type:</form:label></td>
<td><form:input path="type"/></td>
</tr>

<tr>
<td><form:label path="number">Number:</form:label></td>
<td><form:input path="number"/></td>
</tr>
</table>

<input type="submit" value="Save" />
</form:form>

</body>
</html>



Конфігурація.
Ми створили всі необхідні Java класи. Наступний крок — створити необхідні файли конфігурації.

веб.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_ID" version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">

<servlet>
<servlet-name>spring</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
<servlet-name>spring</servlet-name>
<url-pattern>/krams/*</url-pattern>
</servlet-mapping>

<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

</web-app>



spring-servlet.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

<!-- Declare a view resolver -->
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"
p:prefix="/WEB-INF/jsp/" p:suffix=".jsp" />

</beans>



applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">

<!-- Activates various annotations to be detected in bean classes -->
<context:annotation-config />

<!-- Scans the classpath for annotated components that will be auto-registered as Spring beans.
For example @Controller and @Service. Make sure to set the correct base-package-->
<context:component-scan base-package="org.krams.tutorial" />

<!-- Configures the annotation-driven Spring MVC Controller programming model.
Note that, with Spring 3.0, this tag works in Servlet MVC only! -->
<mvc:annotation-driven />

<!-- Load Hibernate related configuration -->
<import resource="hibernate-context.xml" />

</beans>



hibernate-context.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
">

<context:property-placeholder location="/WEB-INF/spring.properties" />

<!-- Enable annotation style of managing transactions -->
<tx:annotation-driven transaction-manager="transactionManager" />

<!-- Declare the Hibernate SessionFactory for retrieving Hibernate sessions -->
<!-- See http://static.springsource.org/spring/docs/3.0.x/javadoc-api/org/springframework/orm/hibernate3/annotation/AnnotationSessionFactoryBean.html --> 
<!-- See http://docs.jboss.org/hibernate/stable/core/api/index.html?org/hibernate/SessionFactory.html -->
<!-- See http://docs.jboss.org/hibernate/stable/core/api/index.html?org/hibernate/Session.html -->
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean"
p:dataSource-ref="dataSource"
p:configLocation="${hibernate.config}"
p:packagesToScan="org.krams.tutorial"/>

<!-- Declare a datasource that has pooling capabilities--> 
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
destroy-method="close"
p:driverClass="${app.jdbc.driverClassName}"
p:jdbcUrl="${app.jdbc.url}"
p:user="${app.jdbc.username}"
p:password="${app.jdbc.password}"
p:acquireIncrement="5"
p:idleConnectionTestPeriod="60"
p:maxPoolSize="100"
p:maxStatements="50"
p:minPoolSize="10" />

<!-- Declare a transaction manager-->
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager"
p:sessionFactory-ref="sessionFactory" />

</beans>



hibernate.cfg.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">

<hibernate-configuration>
<session-factory>
<!-- We're using MySQL database so the dialect needs to MySQL as well-->
<property name="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</property>
<!-- Enable this to see the SQL statements in the logs-->
<property name="show_sql">true</property>
<!-- This will drop our existing database and re-create a new one.
Existing data will be deleted! -->
<property name="hbm2ddl.auto">create</property>
</session-factory>
</hibernate-configuration>



spring.properties
# database properties
app.jdbc.driverClassName=com.mysql.jdbc.Driver
app.jdbc.url=jdbc:mysql://localhost/mydatabase
app.jdbc.username= " root
app.jdbc.password=

#hibernate properties
hibernate.config=/WEB-INF/hibernate.cfg.xml



Запуск програми.
Встановлюємо базу даних.
Наше додаток використовує MySQL в якості бази даних. Для запуску програми переконайтеся, що ви створили базу даних.

Для її створення дотримуйтесь кроків:
1. Відкрийте phpmyadmin (або будь-яку іншу програму для роботи з БД, яку ви віддаєте перевагу)
2. Створюємо нову базу даних mydatabase
3. Запускаємо наш додаток, схему БД воно створить автоматично.

Для перевірки використовуєте файл mydatabase.sql, розташований в каталозі WEB_INF нашої програми.

Для доступу до програми URL:
localhost:8080/spring-hibernate-one-to-many-default/krams/main/record/list

Висновок
Ми створили додатка Spring MVC використовуючи відношення один-до-багатьох і анотації Hibernate. Так само ми обговорили проблеми пов'язані з установками за умовчанням для анотації @OneToMany.

GIT: github.com/sa4ek/spring-hibernate-one-to-many-default

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.