1. 数据持久化
1.1 JPA
在JPA 产生之前,围绕如何简化数据库操作的相关讨论已经是层出不穷,其中ORM 框架最为开发人员所关注。ORM是一种用于实现面向对象编程语言里不同类型系统的数据之间的转换的技术,它们将对象拆分成SQL 语句,从而来操作数据库。但是不同的ORM 框架在使用上存在比较大的差异,而JPA 规范就是为了解决这个问题:规范ORM 框架,使用ORM 框架统一的接口和用法。
最早的JPA 规范是由Java 官方提出的,随Java EE 5 规范一同发布。
1.2 实体
通常,实体表示关系数据库中的表,井且每个实体实例对应于该表中的行。实体的持久状态通过持久化字段或持久化属性来表示,这些字段或属性使用对象/关系映射注解将实体和实体关系映射到基础数据存储中的关系数据。
1.2.1 实体类的要求
( 1 )类必须用@Entity 注解。
( 2 )类必须有一个public 或protected 的元参数的构造函数。该类可以具有其他构造函数。
( 3 )类不能声明为final 。没有方法的或持久化实例变量必须声明为final 。
( 4 )如果实体实例被当作值以分离对象方式进行传递(如通过会话Bean 的远程业务接口),
则该类必须实现Serializable 接口。
( 5 )实体可以扩展实体类或者是非实体类,并且非实体类可以扩展实体类。
( 6 )持久化实例变量必须声明为private 、protected 或package-private ,并且只能通过实体类的
方法直接访问。客户端必须通过访问器或业务方法访问实体的状态。
举个例子:
package com.duxihu.demothymeleaf.domain; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; @Entity public class DemoUser { @Id //主键 @GeneratedValue(strategy = GenerationType.IDENTITY)//自增长策略 private Long id; private String name; private Integer age; protected DemoUser(){//无参构造函数;设为protected防止被直接使用 } public DemoUser(Long id ,String name, Integer age) { this.id = id; this.name = name; this.age = age; } public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } @Override public String toString() { return "DemoUser{" + "id=" + id + ", name='" + name + '\'' + ", age=" + age + '}'; } }
如果实体使用持久化属性, 实体必须遵循JavaBean 组件的方法约定。对于实体的每个持久化属性,都有一个getter 方法和setter方法。如果属性是布尔值,可以使用isProperty ,而不是getProperty 。持久化属性的对象/关系映射注解必须应用于getter方法。映射注解不能应用于注解为@Transient或标记为transient 的字段或属性。
1.2.2 在实体字段和属性中使用集合
集合作为持久化字段和属性,必须使用受Java 集合支持的接口:
• java.util.Collection。
• java.util.Set 。
• java.util.List 。
• java. util.Map 。
如果实体的字段或属性由基本类型或可嵌入类的集合组成,请在字段或属性上使用@ElementCollection 注解。
@ElementCollection protected Set<String> loverName =new HashSet<>();
1.2.3 实体中的主键
实体可以具有简单主键或复合主键。
简单主键使用@Id 注解来表示主键属性或字段。
当主键由多个属性组成时,使用复合主键,复合主键必须在主键类中定义,使用@Embeddedld和@IdClass来标识。
主键或复合主键的属性或字段必须是以下Java 语言类型之一。
Java 基本数据类型。
Java 基本数据类型的包装类型。
java.lang.String 。
java.util.Date (时间类型应为DATE )。
java.sql.Date
java.math.BigDecimal
java.math.Biglnteger
不应在主键中使用浮点类型。
主键类必须满足以下要求
( 1 )类的访问控制修饰符必须是public 。
( 2 ) 如果使用基于属性的访问, 主键类的属性必须为public 或protected 。
( 3 )该类必须有一个公共默认构造函数。
( 4 )类必须实现hashCode()方法和equals(Object other)方法。
( 5 )类必须是可序列化的。
( 6 )复合主键必须被表示并映射到实体类的多个字段或属性,或者必须被表示井映射为可嵌
入类。
( 7 ) 如果类映射到实体类的多个字段或属性, 则主键类中的主键宇段或属性的名称和类型必
须与实体类的名称和类型匹配.
1.2.4 实体间的关系
( 1 ) 一对一 One to One
( 2 ) 一对多 One to Many
( 3 ) 多对一 Many to One
( 4 ) 多对多 Many to Many
实体关系中的方向可以是双向的或单向的。
1. 双向关系 , 双向关系必须遵循以下规则:
( 1 )双向关系的反面必须通过使用@OneToOne @OneToMany 或@ ManyToMany 注解的
mappedBy元素引用其拥有方。mappedBy元素指定实体中作为关系所有者的属性或字段。
( 2 )多对一双向关系的许多方面不能定义mappedBy 元素。许多方面总是关系的拥有方。
( 3 )对于一对一双向关系,拥有侧对应于包含相应外键的一侧。
( 4 )对于多对多双向关系,任一侧可以是拥有侧。
2. 单向关系
只有一个实体具有引用另一个实体的关系字段或属性.
1.2.5 级联操作相关系
使用关系的实体通常依赖于关系中另一个实体的存在,比如用户详情是用户项的一部分 ,
如果用户被删除,用户详情也应该被删除,这称为级联删除关系。
实体的级联操作
ALL : ALL 级联操作将应用于父实体的相关实体。所有等同于指定cascade={DETACH,MERGE, PERSIST, REFRESH, REMOVE}
DETACH: 如果父实体与持久化上下文分离,则相关实体也将被分离
MERGE : 如果父实体被合并到持久化上下文中,则相关实体也将被合并
PERSIST : 如果父实体被持久化到持久化上下文中,则相关实体也将被持久化
REFRESH : 如果父实体在当前持久化上下文中被刷新,相关实体也将被刷新
REMOVE : 如果父实体从当前持久化上下文中删除,相关实体也将被删除
级联删除关系使用@OneToOne 和@OneToMany 关系的cascade=REMOVE 元素指定。例如,
@OneToMany(cascade=REMOVE , mappedBy=”User ”) public Set<UserNumbers> getNumbers () { return numbers ; }
删除关系中的“孤儿”
@OneToMany 和@oneToOne 中的orphanRemoval 属性采用布尔值,默认值为false 。修改为true,则在删除用户后默认删除用户妻子们;
@OneToMany(mappedBy="User", orphanRemoval="true")
public List <UserWifes> getWifes() { ... }
1.2 Spring Data JPA 是对JPA 规范的实现。
对于普通开发者而言,自己实现应用程序的数据访问层是一件极其烦琐的过程。必须编写太多的样板代码来执行简单查询、分页和审计。
Spring Data JPA 包含如下特征。
( 1 )基于Spring 和JPA 来构建复杂的存储库。
( 2 )支持Querydsl谓词,因此支持类型安全的JPA查询。
( 3 )域类的透明审计。
( 4 ) 具备分页支持、动态查词执行、集成自定义数据访问代码的能力。
( 5 )在引导时验证@ Query 带注释的查询。
( 6 )支持基于XML 的实体映射。
( 7 )通过引人@EnableJpaRepositories 来实现基于JavaConfig 的存储库配置。
如何使用Spring Data JPA,首先引入依赖
<dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-jpa</artifactId> <version>1.10.5.RELEASE</version> </dependency>
对于底层数据存储的管理,通常使用标准CRUD 功能的资惊库来实现。
使用Spring Data 声明,这些查询将会变得简单,只需要4 步过程
1. 声明扩展Repository或子接口
2 . 在接口上声明查询方法
3. 为这些接口创建代理实例
例如一下只需声明继承自Spring Data JPA 中的接口,就能自动实现通过名称来模糊查询的方法,而无需自己去实现。
public interface UserRepository extends JpaRepository<User , Long>{ List<User> findUsersByNameLike (String name); }
通常,存储库接口将会扩展Repository 、CrudRepository 或PagingAndSortingRepository 等这些Spring Data 接口。另外,如果不想继承Spring Data 接口,还可以在接口上添加@RepositoryDefinition 注解, 用以声明这是一个Repos itory 接口。
3. Spring Data JPA 与Hibernate 、Spring Boot 集成
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency>
1.定义实体
修改User 类,参考JPA 的规范,将其修改成为实体。
( 1 ) User 类上增加了@Entity 注解,以标识其为实体。
( 2 ) @Id 标识id 字段为主键。
( 3 ) @GeneratedValue(strategy=GenerationType. IDENTITY)标识id 字段,以便使用数据库的自
增长字段为新增加的实体的标识。这种情况下需要数据库提供对自增长字段的支峙, 一般的数据库
(如HSQL 、SQL Server 、MySQL 等)都能够提供这种支持。
( 4)应JPA 的规范要求,设置无参的构造函数protected User() {},并设为protected ,防止直
接被使用。
( 5)重写toString 方法,将User 信息自定义输出。
package com.duxihu.demothymeleaf.domain; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; @Entity //实体 public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY)//自增长策略 private Long id;//唯一标识 private String name; private String email; protected User() {//无参默认构造器 } public User(Long id, String name, String email) { this.id = id; this.name = name; this.email = email; } public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } @Override public String toString() { return "User{" + "id=" + id + ", name='" + name + '\'' + ", email='" + email + '\'' + '}'; } }
2 .修改资源库
修改用户资源库的接口,继承CrudRepository 。由于Spring Data JPA 已经帮助用户做了实现,因此,用户不需要做任何实现,甚至都无须在UserRepository 中定义任何方法
public interface UserRepository extends CrudRepository<User , Long> { }
3. 修改控制器
@RestController @RequestMapping("/user") public class UserController { @Autowired private UserRepository userRepository; //查词所有用户 @GetMapping("/userlist") public ModelAndView userList(Model model){ model.addAttribute("userList",userRepository.findAll()); model.addAttribute("title","用户管理"); return new ModelAndView("user/list","userModel",model); } //根据id 查询用户 @GetMapping("{id}") public ModelAndView view(@PathVariable("id") Long id, Model model){ Optional<User> user= userRepository.findById(id); model.addAttribute("user",user.get()); model.addAttribute("title","查看用户"); return new ModelAndView("user/view" ,"userModel",model); } //获取创建表单页面 @GetMapping("/form") public ModelAndView createForm(Model model){ model.addAttribute("user",new User(null,null,null)); model.addAttribute("title","创建用户"); return new ModelAndView("user/form","userModel",model); } //保存用户 @PostMapping public ModelAndView saveOrUpdateUser(User user){ user =userRepository.save(user); return new ModelAndView("redirect:/user/userlist"); } //根据id删除用户 @GetMapping(value = "delete/{id}") public ModelAndView delete(@PathVariable("id") Long id){ userRepository.deleteById(id); return new ModelAndView("redirect:/user/userlist"); } //修改用户界面 @GetMapping(value = "edit/{id}") public ModelAndView editForm(@PathVariable("id") Long id,Model model){ Optional<User> user =userRepository.findById(id); model.addAttribute("user",user); model.addAttribute("title","编辑用户"); return new ModelAndView("user/form" ,"userModel",model); } }
使用MySQL 数据库
首先,创建名为test的数据库,编码为UTF-8 ,修改application.properties文件,增加数据库连接
# DataSource spring.datasource.url=jdbc:mysql://localhost/test?useSSL=false&serverTimezone=UTC&characterEncoding=utf-8 #注意不是spring.datasource.data-username #注意不是spring.datasource.data-password spring.datasource.username=test spring.datasource.password=test spring.datasource.driver-class-name=com.mysql.jdbc.Driver # JPA spring.jpa.show-sql = true #每次应用启动,都会主动删除井创建数据库表 spring.jpa.hibernate.ddl-auto=create-drop
其中spring.datasource.driver-class-name根据你当前mysql版本会有所不同。
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
5.7 以上的 mysql 要注意在url中增加 useSSL=false&serverTimezone=UTC选项
连接数据库的用户名和密码是 spring.datasource.username而不是spring.datasource.data-username。
启动项目
可以发现, Hibernate 会自动在test数据库中创建表user。
通过浏览器访问http://localhost:8080/useruserlist可以看到项目的运行效果.