Статьи

Spring — слой постоянства — написание сущностей и настройка Hibernate

Добро пожаловать во вторую часть этого урока. Не волнуйтесь, когда увидите, как долго эта статья — я обещаю, что это в основном простые POJO и какой-то сгенерированный код.

Прежде чем мы начнем, нам нужно обновить наши зависимости Maven, потому что сейчас мы будем использовать Hibernate и Spring. Добавьте следующие зависимости в ваш pom.xml :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<dependency>
 <groupId>org.hibernate</groupId>
 <artifactId>hibernate-entitymanager</artifactId>
 <version>3.6.8.Final</version>
</dependency>
              <dependency>
 <groupId>mysql</groupId>
 <artifactId>mysql-connector-java</artifactId>
 <version>5.1.6</version>
</dependency>
 
<!-- spring framework -->
<dependency>
 <groupId>org.springframework</groupId>
 <artifactId>spring-context</artifactId>
 <version>3.1.0.RELEASE</version>
</dependency>
              <dependency>
 <groupId>org.springframework</groupId>
 <artifactId>spring-test</artifactId>
 <version>3.1.0.RELEASE</version>
</dependency>
<dependency>
 <groupId>org.springframework</groupId>
 <artifactId>spring-jdbc</artifactId>
 <version>3.1.0.RELEASE</version>
</dependency>
<dependency>
 <groupId>org.springframework</groupId>
 <artifactId>spring-orm</artifactId>
 <version>3.1.0.RELEASE</version>
</dependency>
<dependency>
 <groupId>org.springframework</groupId>
 <artifactId>spring-webmvc</artifactId>
 <version>3.1.0.RELEASE</version>
</dependency>
<dependency>
 <groupId>org.springframework</groupId>
 <artifactId>spring-web</artifactId>
 <version>3.1.0.RELEASE</version>
</dependency>
              <dependency>
 <groupId>org.springframework</groupId>
 <artifactId>spring-web</artifactId>
 <version>3.1.0.RELEASE</version>
</dependency>

Если вы новичок в Maven, возможно, вам сейчас интересно — откуда вы это знаете? Где их взять? Ну, просто зайдите на http://mvnrepository.com/ и введите то, что ищет ваш эль. Вы получите полный код для зависимостей maven. Если вы когда-нибудь пытались собрать приложение Spring или Hibernate самостоятельно, не используя Maven, вы, вероятно, знаете, насколько это было больно. С Maven все намного проще.

Также обратите внимание, что мы включили зависимость от коннектора MySQL. Если вы решили использовать другую базу данных, не забудьте изменить это.

С Hibernate у нас есть 2 варианта, как превратить наши POJO в энтиты. Либо мы используем XML и создаем файлы сопоставления , либо мы добавим метаданные в наш код (аннотации Java). Некоторые люди боятся этого и считают это связыванием со структурой. Это правда, что вам понадобятся аннотации javax.persistence на вашем пути к классам, но мы не будем реализовывать интерфейсы или расширять каркасные классы. Мы просто добавим некоторую метаинформацию в наш код, а POJO по-прежнему будут просто POJO с некоторой дополнительной информацией.

Мы сейчас превратим наши POJO в юридические лица. Нам нужно будет сделать следующие изменения:

  • Добавить конструктор по умолчанию без аргументов для Hibernate
  • Создать геттеры и сеттеры для полей
  • добавить методы equals и hashCode .
  • Добавьте аннотации постоянства. Обратите внимание, что мы также используем аннотацию @Table, чтобы различать соглашения об именах Java и SQL.
  • Добавьте поля идентификатора. Это будут первичные ключи в нашей реляционной базе данных.

Это довольно много стандартного кода, так что пусть ваша IDE поможет вам. Большинство современных IDE генерируют для вас конструкторы, геттеры, сеттеры, equals и hashCode.

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
package org.timesheet.domain;
 
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
 
@Entity
@Table(name = 'employee')
public class Employee {
 
 @Id
 @GeneratedValue(strategy=GenerationType.IDENTITY)
 private Long id;
 private String name;
 private String department;
  
 public Employee() {
 }
 
 public Employee(String name, String department) {
  this.name = name;
  this.department = department;
 }
  
 public String getName() {
  return name;
 }
  
 public String getDepartment() {
  return department;
 }
  
 public Long getId() {
  return id;
 }
  
 public void setId(Long id) {
  this.id = id;
 }
 
 public void setName(String name) {
  this.name = name;
 }
 
 public void setDepartment(String department) {
  this.department = department;
 }
 
 @Override
 public String toString() {
  return 'Employee [id=' + id + ', name=' + name + ', department='
    + department + ']';
 }
 
 @Override
 public int hashCode() {
  final int prime = 31;
  int result = 1;
  result = prime * result
    + ((department == null) ? 0 : department.hashCode());
  result = prime * result + ((id == null) ? 0 : id.hashCode());
  result = prime * result + ((name == null) ? 0 : name.hashCode());
  return result;
 }
 
 @Override
 public boolean equals(Object obj) {
  if (this == obj) {
   return true;
  }
  if (obj == null) {
   return false;
  }
  if (!(obj instanceof Employee)) {
   return false;
  }
  Employee other = (Employee) obj;
  if (department == null) {
   if (other.department != null) {
    return false;
   }
  } else if (!department.equals(other.department)) {
   return false;
  }
  if (id == null) {
   if (other.id != null) {
    return false;
   }
  } else if (!id.equals(other.id)) {
   return false;
  }
  if (name == null) {
   if (other.name != null) {
    return false;
   }
  } else if (!name.equals(other.name)) {
   return false;
  }
  return true;
 }
  
}
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
package org.timesheet.domain;
 
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
 
@Entity
@Table(name = 'manager')
public class Manager {
  
 @Id
 @GeneratedValue(strategy=GenerationType.IDENTITY)
 private Long id;
 private String name;
  
 public Manager() {
 }
 
 public Manager(String name) {
  this.name = name;
 }
  
 public String getName() {
  return name;
 }
 
 public Long getId() {
  return id;
 }
 
 public void setId(Long id) {
  this.id = id;
 }
 
 public void setName(String name) {
  this.name = name;
 }
 
 @Override
 public int hashCode() {
  final int prime = 31;
  int result = 1;
  result = prime * result + ((id == null) ? 0 : id.hashCode());
  result = prime * result + ((name == null) ? 0 : name.hashCode());
  return result;
 }
 
 @Override
 public boolean equals(Object obj) {
  if (this == obj) {
   return true;
  }
  if (obj == null) {
   return false;
  }
  if (!(obj instanceof Manager)) {
   return false;
  }
  Manager other = (Manager) obj;
  if (id == null) {
   if (other.id != null) {
    return false;
   }
  } else if (!id.equals(other.id)) {
   return false;
  }
  if (name == null) {
   if (other.name != null) {
    return false;
   }
  } else if (!name.equals(other.name)) {
   return false;
  }
  return true;
 }
 
 @Override
 public String toString() {
  return 'Manager [id=' + id + ', name=' + name + ']';
 }
  
}
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
package org.timesheet.domain;
 
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToOne;
import javax.persistence.Table;
 
@Entity
@Table(name='timesheet')
public class Timesheet {
 
 @Id
 @GeneratedValue(strategy=GenerationType.IDENTITY)
 private Long id;
  
 @OneToOne
 @JoinColumn(name = 'employee_id')
 private Employee who;
  
 @OneToOne
 @JoinColumn(name = 'task_id')
 private Task task;
 private Integer hours;
  
 public Timesheet() {
 }
  
 public Timesheet(Employee who, Task task, Integer hours) {
  this.who = who;
  this.task = task;
  this.hours = hours;
 }
 
 public Employee getWho() {
  return who;
 }
 
 public Task getTask() {
  return task;
 }
  
 public Integer getHours() {
  return hours;
 }
  
 public Long getId() {
  return id;
 }
 
 public void setId(Long id) {
  this.id = id;
 }
 
 public void setWho(Employee who) {
  this.who = who;
 }
 
 public void setTask(Task task) {
  this.task = task;
 }
 
 public void setHours(Integer hours) {
  this.hours = hours;
 }
 
 /**
  * Manager can alter hours before closing task
  * @param hours New amount of hours
  */
 public void alterHours(Integer hours) {
  this.hours = hours;
 }
 
 @Override
 public String toString() {
  return 'Timesheet [id=' + id + ', who=' + who + ', task=' + task
    + ', hours=' + hours + ']';
 }
 
 @Override
 public int hashCode() {
  final int prime = 31;
  int result = 1;
  result = prime * result + ((hours == null) ? 0 : hours.hashCode());
  result = prime * result + ((id == null) ? 0 : id.hashCode());
  result = prime * result + ((task == null) ? 0 : task.hashCode());
  result = prime * result + ((who == null) ? 0 : who.hashCode());
  return result;
 }
 
 @Override
 public boolean equals(Object obj) {
  if (this == obj) {
   return true;
  }
  if (obj == null) {
   return false;
  }
  if (!(obj instanceof Timesheet)) {
   return false;
  }
  Timesheet other = (Timesheet) obj;
  if (hours == null) {
   if (other.hours != null) {
    return false;
   }
  } else if (!hours.equals(other.hours)) {
   return false;
  }
  if (id == null) {
   if (other.id != null) {
    return false;
   }
  } else if (!id.equals(other.id)) {
   return false;
  }
  if (task == null) {
   if (other.task != null) {
    return false;
   }
  } else if (!task.equals(other.task)) {
   return false;
  }
  if (who == null) {
   if (other.who != null) {
    return false;
   }
  } else if (!who.equals(other.who)) {
   return false;
  }
  return true;
 }
}

И наконец, вот сущность Task, когда нам нужно было также использовать отображение @ManyToMany. Это связано с тем, что один сотрудник может выполнять несколько задач, а одной задаче может быть назначено несколько сотрудников. Мы определили, как будет выглядеть наше m: n , используя аннотации @JoinTable и @JoinColumn.

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
package org.timesheet.domain;
 
import javax.persistence.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
 
@Entity
@Table(name = 'task')
public class Task {
 
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
 
    @ManyToMany(fetch = FetchType.EAGER)
    @JoinTable(name = 'task_employee',
            joinColumns = {@JoinColumn(name = 'task_id')},
            inverseJoinColumns = {@JoinColumn(name = 'employee_id')}
    )
    private List<Employee> assignedEmployees = new ArrayList<Employee>();
 
    @OneToOne
    @JoinColumn(name = 'manager_id')
    private Manager manager;
 
    private String description;
    boolean completed;
 
    public Task() {
    }
 
    public Task(String description, Manager manager, Employee... employees) {
        this.description = description;
        this.manager = manager;
        assignedEmployees.addAll(Arrays.asList(employees));
        completed = false;
    }
 
    public Manager getManager() {
        return manager;
    }
 
    public List<Employee> getAssignedEmployees() {
        return assignedEmployees;
    }
 
    public void addEmployee(Employee e) {
        assignedEmployees.add(e);
    }
 
    public void removeEmployee(Employee e) {
        assignedEmployees.remove(e);
    }
 
    public Long getId() {
        return id;
    }
 
    public void setId(Long id) {
        this.id = id;
    }
 
    public boolean isCompleted() {
        return completed;
    }
 
    public void setCompleted(boolean completed) {
        this.completed = completed;
    }
 
    public void setAssignedEmployees(List<Employee> assignedEmployees) {
        this.assignedEmployees = assignedEmployees;
    }
 
    public void setManager(Manager manager) {
        this.manager = manager;
    }
 
    public String getDescription() {
        return description;
    }
 
    public void setDescription(String description) {
        this.description = description;
    }
 
    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
 
        Task task = (Task) o;
 
        if (completed != task.completed) {
            return false;
        }
        if (description != null ? !description.equals(task.description) : task.description != null) {
            return false;
        }
        if (id != null ? !id.equals(task.id) : task.id != null) {
            return false;
        }
        if (manager != null ? !manager.equals(task.manager) : task.manager != null) {
            return false;
        }
 
        return true;
    }
 
    @Override
    public int hashCode() {
        int result = id != null ? id.hashCode() : 0;
        result = 31 * result + (manager != null ? manager.hashCode() : 0);
        result = 31 * result + (description != null ? description.hashCode() : 0);
        result = 31 * result + (completed ? 1 : 0);
        return result;
    }
 
    @Override
    public String toString() {
        return 'Task{' +
                'id=' + id +
                ', assignedEmployees=' + assignedEmployees +
                ', manager=' + manager +
                ', description='' + description + '\'' +
                ', completed=' + completed +
                '}';
    }
}

Таким образом, мы не делали ничего особенного для модели. Если вам захочется посмотреть UML на следующую картинку, отношения будут такими же, как и раньше.

Хорошо, у нас есть энтузиасты, теперь давайте создадим базу данных. Выберите какой-нибудь инструмент для управления базой данных (даже простой терминал в порядке) и создайте базу данных расписаний следующим образом (по умолчанию mysql установит в / usr / local / mysql / bin / mysql в Mac OS X):

1
2
$ mysql -u root
mysql > create database timesheet;

Если вы когда-либо настраивали Hibernate до того, как вы, вероятно, знаете, вам понадобится довольно много файлов и шаблонного кода при работе, например, с SessionFactory. Эти вещи намного проще с Spring.

Теперь мы создадим наш первый файл конфигурации Spring Bean — это файл, в котором мы регистрируем bean-компоненты для контейнера Spring. Если бы мне пришлось объяснить, что это за файл кому-то, кто вообще не знает, что такое Spring — это своего рода волшебный пакет, в котором контейнер Spring может находить объекты.

Современные IDE помогут вам правильно получить все пространства имен XML, например, вы можете видеть изображения из мастера STS. В NetBeans есть нечто похожее, и IntelliJ разрешает пространства имен на лету.

Назовите файл конфигурации persistence-beans.xml, и мы поместим его в папку src / main / resources .

Поэтому настроить Hibernate, транзакции, конфигурацию аннотаций и т. Д. Так же просто, как создать несколько bean-компонентов в XML-файле. В качестве альтернативы, мы можем использовать Java Config для Spring, но XML-конфигурации все еще используются гораздо больше, поэтому мы будем придерживаться их. Я не хочу отговаривать вас от использования Java Config! Конфигурация XML намного более популярна в данный момент, но я не могу гарантировать это в течение следующих нескольких лет.
Я прокомментировал каждый компонент, чтобы убедиться, что вы понимаете, что мы там делали, прежде чем продолжить. Если вы хотите получить некоторое визуальное представление о связях между компонентами, вы можете снова использовать некоторые инструменты — в STS это называется Bean Graph, в IntelliJ это зависимости. Вы можете увидеть образец зависимостей на картинке ниже.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<?xml version='1.0' encoding='UTF-8'?>
  
 <!-- we can use annotations -->
 <context:annotation-config /> 
  
 <!-- package to look for annotated classes -->
 <context:component-scan base-package='org.timesheet.service.impl' />
  
 <!-- we will manage transactions with annotations -->
 <tx:annotation-driven />
 
 <!-- data source for our database -->
 <bean id='dataSource'
  class='org.springframework.jdbc.datasource.DriverManagerDataSource'>
  <property name='driverClassName'
   value='com.mysql.jdbc.jdbc2.optional.MysqlDataSource' />
  <property name='url' value='jdbc:mysql://localhost/timesheet' />
  <property name='username' value='root' />
  <property name='password' value='' />
 </bean>
  
 <!-- configure hibernate session factory -->
 <bean id='sessionFactory'
  class='org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean'>
  <property name='dataSource' ref='dataSource' />
  <property name='annotatedClasses' >
   <list>
    <value>org.timesheet.domain.Employee</value>
    <value>org.timesheet.domain.Manager</value>
    <value>org.timesheet.domain.Task</value>
    <value>org.timesheet.domain.Timesheet</value>
   </list>
  </property>
  <property name='hibernateProperties'>
   <props>
    <prop key='dialect'>org.hibernate.dialect.MySQL5InnoDBDialect</prop>
    <prop key='hibernate.show_sql'>true</prop>
    <prop key='hibernate.hbm2ddl.auto'>update</prop>
   </props>
  </property>
 </bean>
  
</beans>

Хорошо, это было довольно много конфигурации, он? Что не так хорошо, так это то, что мы поместили имена наших сущностей в XML в виде простого текста, поэтому он не подходит для рефакторинга. Но я думаю, что для этого урока это приемлемо :) Давайте напишем интеграционный тест для Hibernate, чтобы мы знали, что все настроено правильно.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package org.timesheet.integration;
 
import static org.junit.Assert.*;
 
import org.hibernate.SessionFactory;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests;
 
@ContextConfiguration(locations = '/persistence-beans.xml')
public class HibernateConfigurationTest extends AbstractJUnit4SpringContextTests {
  
 @Autowired
 private SessionFactory sessionFactory;
 
 @Test
 public void testHibernateConfiguration() {
  // Spring IOC container instantiated and prepared sessionFactory
  assertNotNull (sessionFactory);
 }
 
}

Я хочу, чтобы вы отметили 2 вещи здесь. Сначала мы расширяем класс AbstractJUnit4SpringContextTests . Мы говорим ему, где он должен искать актуальный XML-конфиг с пружинными компонентами. В противном случае нам пришлось бы создавать контейнер Spring самостоятельно, что означает дополнительный шаблонный код.

Во-вторых, мы используем аннотацию @Autowired. Это означает, что мы не создаем экземпляр SessionFactory вручную, используя оператор new , но у нас будет его Autowired (Injected) из контейнера Spring! Это одна из самых важных целей контейнера Spring — зависеть от интерфейсов и внедрять реализации, а не создавать их вручную.
Все должно работать сейчас, и я думаю, что этого достаточно для этой части.

Если вам нравится, вы можете проверить простой SQL и увидеть таблицы здесь, сделайте это так:

01
02
03
04
05
06
07
08
09
10
11
12
mysql> use timesheet;
mysql> show tables;
+---------------------+
| Tables_in_timesheet |
+---------------------+
| employee            |
| manager             |
| task                |
| task_employee       |
| timesheet           |
+---------------------+
5 rows in set (0.00 sec)

Ссылка: Часть 2. Уровень персистентности — написание сущностей и настройка Hibernate от нашего партнера по JCG Михала Вртиака в блоге vrtoonjava .