一、JPA
在介绍Spring Data JPA的时候,我们首先认识下Hibernate。Hibernate是数据访问解决技术的绝对II主,使用0/R映射(Object-Relational Mapping)技术实现数据访问,O/R映射即将领域模型类和数据库的表进行映射,通过程序操作对象而实现表数据操作的能力,让数据访问操作无须关注数据库相关的技术。
随着Hibernate的盛行,Hibernate主导了EJB3.0的JPA规范,JPA即Java Persistence API。JPA是一个基于0/R映射的标准规范(目前最新版本是JPA2.1)。所谓规范即只定义标准规则(如注解、接口),不提供实现,软件提供商可以按照标准规范来实现,而使用者只需按照规范中定义的方式来使用,而不用和软件提供商的实现打交道。JPA的主要实现由Hibernate、Eclipse Link和OpenJPA等,这也意味着我们只要使用JPA来开发,无论是哪一个开发方式都是一样的。
Spring Data JPA是Spring Data的一个子项目,它通过提供基于JPA的Repository极大地减少了JPA作为数据访问方案的代码量。
在JPA中主要是使用函数名来分别数据库操作的,比如findByUsername
,就是通过username查找记录,再比如findByUsernameAndPassword就是通过用户名密码访问数据,所以在JPA 中,函数的取名还是比较重要的,这部分也可通过代码提示来查看,因为他会根据数据库表字段和一些关键字推荐可用的部分函数名。当然JPA中也有一些原先就定义好的方法,比如findAll,save等方法。
在JPA定义函数名需要遵循下列规定:
Keyword | Sample | SQL |
---|---|---|
And | findByLastnameAndFirstname | where x.lastname = ?1 and x.firstname = ?2 |
Or | findByLastnameOrFirstname | where x.lastname = ?1 or x.firstname = ?2 |
Is,Equals | findByFirstname findByFirstnameIs findByFirstnameEquals |
where x.firstname = 1? |
Between | findByStartDateBetween | where x.startDate between 1? and ?2 |
LessThan | findByAgeLessThan | where x.age < ?1 |
LessThanEqual | findByAgeLessThanEqual | where x.age <= ?1 |
GreaterThan | findByAgeGreaterThan | where x.age > ?1 |
GreaterThanEqual | findByAgeGreaterThanEqual | where x.age >= ?1 |
After | findByStartDateAfter | where x.startDate > ?1 |
Before | findByStartDateBefore | where x.startDate < ?1 |
IsNull | findByAgeIsNull | where x.age is null |
IsNotNull,NotNull | findByAge(Is)NotNull | where x.age not null |
Like | findByFirstnameLike | where x.firstname like ?1 |
NotLike | findByFirstnameNotLike | where x.firstname not like ?1 |
StartingWith | findByFirstnameStartingWith | where x.firstname like ?1 (parameter bound with appended %) |
EndingWith | findByFirstnameEndingWith | where x.firstname like ?1 (parameter bound with prepended %) |
Containing | findByFirstnameContaining | where x.firstname like ?1 (parameter bound wrapped in %) |
OrderBy | findByAgeOrderByLastnameDesc | where x.age = ?1 order by x.lastname desc |
Not | findByLastnameNot | where x.lastname <> ?1 |
In | findByAgeIn(Collection ages) | where x.age in ?1 |
NotIn | findByAgeNotIn(Collection age) | where x.age not in ?1 |
True | findByActiveTrue() | where x.active = true |
False | findByActiveFalse() | where x.active = false |
IgnoreCase | findByFirstnameIgnoreCase | where UPPER(x.firstame) = UPPER( |
二、配置
pom.xml
文件中添加jpa依赖的包,很数据库连接库,这里使用的是mysql数据库。
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
配置连接信息,在application.yml
文件中添加数据库连接的username,password等信息。这部分信息和之前在SpringMVC中的信息几乎是相同的。
spring:
datasource:
url: jdbc:mysql://localhost:3306/test?serverTimezone=UTC
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
jpa:
database: MYSQL
hibernate:
ddl-auto: update
show-sql: true
SpringBoot中默认的连接池好像是org.apache.tomcat.jdbc.pool.DataSource。
在yml文件的代码提示中支持的连接池好像还有有dbcp,dbcp2,tomcat,hikari,但是按照提示之后好像在控制台看不到dbcp,dbcp2,tomcat,hikari这几个关键词,所以也不知道配置是否成功。
这里我还自己配置了阿里云的Druid连接池,就目前查看资料,感觉比较好的连接池有Druid和hikari,但是在实际中是哪几个比较好还不好说,下面是Druid的配置。
在pom.xml
文件中添加所需要的依赖库:
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.2</version>
</dependency>
然后在application.yml
配置下面的信息
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
url: jdbc:mysql://localhost:3306/test?serverTimezone=UTC
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
jpa:
database: MYSQL
hibernate:
ddl-auto: update
show-sql: true
在springboot中配置这部分的信息相对而言还是比较简洁的
三、数据库映射类
package com.example.demo_2.JPA;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.NamedQuery;
//用下面这个注解也是可以的
//import org.hibernate.annotations.NamedQuery;
@Entity
@NamedQuery(name="Test.hhh",query="select t from Test t where t.username=? and t.password=?")
public class Test {
private int id;
private String username;
private String password;
public Test() {
}
public Test(int id, String username, String password) {
this.id = id;
this.username = username;
this.password = password;
}
public Test(String username, String password) {
super();
this.username = username;
this.password = password;
}
@Id
@GeneratedValue
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
这个映射类还是很简单的,主要是上面有一个@NamedQuery
注解,使用这个注解是可以自定义SQL语句来对数据库进行操作的。然后根据name(这里的name是hhh,不是Test.hhh)来调用这部分信息。
四、Repository类
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
public interface TestRepository extends JpaRepository<Test, Integer> {
/**
* select * from Test t where t.username = ?
*
* @param username
* @return
*/
public List<Test> findByUsername(String username);
/**
* select * from Test t where t.username = ? and t.password = ?
*
* @param username
* @param password
* @return
*/
public List<Test> findByUsernameAndPassword(String username, String password);
/**
* 自定义query
*
* @return
*/
@Query("select t from Test t where t.id=:id")
public List<Test> findTestById(@Param("id") int id);
/**
* 与Test的NamedQuery相对应
*
* @param username
* @param password
* @return
*/
public List<Test> hhh(String username, String password);
}
这里面就是实现与操作数据库的操作。继承的JpaRepository中的前一个为数据库表对应的实体类,第二个为该表的主键的类型。看到最后有何叫hhh
的函数,这个就是之前在Test类中自定义的SQL查询语句。然后在Jpa中自定义还可以在本类中是实现,就是findTestById
,在函数名之前用@Query自定义SQL语句。
五、数据库的增删改查
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
public class TestController {
@Autowired
private TestRepository testRepository;
/**
* 通过username获取Test数据
*
* @param username
* @return
*/
@GetMapping(value = "/testController/custom/{username}")
public List<Test> getbyUsername(@PathVariable("username") String username) {
return testRepository.findByUsername(username);
}
/**
* 测试通过函数名构建查询
*
* @param username
* @param password
* @return
*/
@RequestMapping("/testController/custom_1")
public List<Test> getByUsernameAndPassword(@RequestParam("username") String username,
@RequestParam("password") String password) {
return testRepository.findByUsernameAndPassword(username, password);
}
/**
* 通过Repository自定义SQL语句
*
* @param id
* @return
*/
@RequestMapping("/testController/custom_2")
public List<Test> getTestById(@RequestParam("id") int id) {
return testRepository.findTestById(id);
}
/**
* 通过Test类中的NamedQuery自定义查询语句
*
* @param username
* @param password
* @return
*/
@RequestMapping("/testController/custom_3")
public List<Test> hhh(@RequestParam("username") String username, @RequestParam("password") String password) {
return testRepository.hhh(username, password);
}
/**
* 排序
*
* @return
*/
@GetMapping(value = "/testController/custom/sort")
public List<Test> getbyUsernameSort() {
return testRepository.findAll(new Sort(Direction.ASC, "id"));
}
/**
* 分页+排序
*
* @return
*/
@GetMapping(value = "/testController/custom/pageable")
public Page<Test> getbyUsernamePage() {
// return testRepository.findAll(new PageRequest(1,2));
return testRepository.findAll(new PageRequest(1, 2, new Sort(Direction.ASC, "id")));
}
/**
* 获取所有数据
*
* @return
*/
@GetMapping(value = "/testController")
public List<Test> getAll() {
return testRepository.findAll();
}
/**
* Post测试,添加记录
* @param username
* @param password
* @return
*/
@PostMapping(value = "/testController")
public Test getAll(@RequestParam("username") String username, @RequestParam("password") String password) {
Test test = new Test();
test.setPassword(password);
test.setUsername(username);
return testRepository.save(test);
}
/**
* 测试自带的函数
* @param id
* @return
*/
@GetMapping(value = "/testController/{id}")
public Test getOne(@PathVariable("id") int id) {
return testRepository.findOne(id);
}
/**
* 测试删除数据
* @param id
*/
@DeleteMapping(value = "/testController/{id}")
public void deleteOne(@PathVariable("id") int id) {
testRepository.delete(id);
}
}
在这个类中主要是调用TestRepository中的方法。然后启动SpringBoot服务,分别访问对应的URL就可以得到对应的数据。在本类中,还有两个部分,分别是排序和分页这部分的内容,在JPA中也得到了很好地支持,这部分东西看代码还是容易看的,就不做解释了。
六、事务管理Transactional
Transactional中的属性主要是有下面几个。
属性 | 含义 |
---|---|
Propagation (默认REQUIRED) |
Propagation定义了事务的生命周期主要有以下选项: REQUIRED:方法A调用时没有事务新建一个事务,当在方法A调用另外一个方法B的时候,方法B将使用相同的事务;如果方法B发生异常需要数据回滚的时候,整个事务数据回滚 REQUIRES_NEW:对于方法A和B,在方法调用的时候无论是,否有事务都开启一个新的事务;这样如果方法B有异常不会导致,方法A的数据回滚 NESTED:和REQUIRES_NEW类似,但支持JDBC,不支持JPA或Hibernate SUPPORTS:方法调用时有事务就用事务,没事务就不用事务 NOT_SUPPORTED:强制方法不在事务中执行,若有事务,在方法调用到结束阶段事务都将会被挂起 NEVER:强制方法不在事务中执行,若有事务则抛出异常 MANDATORY:强制方法在事务中执行,若无事务则抛出异常 |
Isolation (默认DEFAULT) |
Isolation(隔离)决定了事务的完整性,处理在多事务对相同数据下的处理机制,主要包含下面的隔离级别(当然我们也不可以随意设置,这要看当前数据库是否支持) READ_UNCOMMITTED:对于在A事务里修改了一条记录但没有提交事务,在B事务可以读取到修改后的记录。可导致脏读、不可重复读以及幻读 READ_COMMITTED:只有当在A事务里修改了一条记录且提交事务之后,B事务才可以读取到提交后的记录;阻止脏读,但可能导致不可重复读和幻读 REPEATABLE_READ:不仅能实现 READ_COMMITTED 的功能,而且还能阻止到A事务读取了一条记录,B事务将不允许修改这条记录;阻止脏读和不可重复读,但可出现幻读 SERIALIZABLE:此级別下亊务足顺序执行的,可以避免上述级别的缺陷,似开销较大 DEFAULT:使用当前数据库的默认隔离界级别,如Oracle,SQL Server 是 READ_COMMITTED; Mysql是REPEATABLE_READ |
timeout | timeout指定事务过期时间,默认为当前数据库的事务过期时间 |
readOnly | 指定当前事务是否只读事务,默认false |
rollbackFor | 指定哪个或者哪些异常可以引起事务回滚 |
noRollbackFor | 指定哪个或者哪些异常不可以引起事务回滚 |
但是在这里很多属性是不方便测试的,这里就不做测试了。
在这里需要注意的是使用的是org.springframework.transaction.annotation.Transactional;
包中的Transactional注解,若是使用javax.transaction.Transactional;
的注解,该包中只有rollbackOn和dontRollbackOn这儿两个属性,其他的属性是没有的,当然使用这个包中的这两个属性也是完成可以的,但是其他的属性就没有办法测试了。
下面是测试代码(是在上面的那个部分完成之后才能做这个的)
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class TranscationalController {
@Autowired
private TestRepository testRepository;
/**
* rollbackFor 抛出异常之后回滚数据,即新纪录不会被写进数据库
*
* @Transactional 使用的是org.springframework.transaction.annotation.Transactional;
* 不是 javax.transaction.Transactional;
*/
@RequestMapping("/transaction_1")
@Transactional(rollbackFor = { IllegalArgumentException.class })
public void transaction_1() {
testRepository.save(new Test("transaction_1", "transaction_1"));
throw new IllegalArgumentException("\rollbackFor");
}
/**
* noRollbackFor 抛出异常之后,不会滚数据,即新纪录还是会被写进数据库
*/
@RequestMapping("/transaction_2")
@Transactional(noRollbackFor = { IllegalArgumentException.class })
public void transaction_2() {
testRepository.save(new Test("transaction_2", "transaction_2"));
throw new IllegalArgumentException("\noRollbackFor");
}
/**
* readOnly = true
* 会出现异常:Queries leading to data modification are not allowed
*/
@RequestMapping("/transaction_3")
@Transactional(readOnly = true)
public Test transaction_3() {
return testRepository.save(new Test("transaction_3", "transaction_3"));
}
/**
* 其他的与transaction相关的还有isolation,timeout和propagation,这三个不好测试,感觉用默认的就挺好用的了
*/
}
七、缓存
注解 | 解释 |
---|---|
@Cacheable | 若缓存中已经有数据,则直接读取数据,若无,则将内容存进缓存 |
@CachePut | 不管缓存中是否存在该值,都会将把内容存进缓存 |
@CacheEvict | 删除缓存 |
@Caching | 可以通过该注解组合多个注解策略在一个方法上 |
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* 需要在Demo2Application.java中添加@EnableCaching注解
* @Caching 可以通过该注解组合多个注解策略在一个方法上
* JPA中注解几乎都是通过AOP的方式使用的
*/
@RestController
public class CacheController {
@Autowired
private TestRepository testRepository;
/**
* 不管怎么样都会将把内容存进缓存
*
* @param id
* @return
*/
@RequestMapping("/cache_1")
@CachePut(value = "test", key = "#id")
public Test CachePut(@RequestParam("id") int id) {
return testRepository.findOne(id);
}
/**
* 若缓存中已经有数据,则直接读取数据,若无,则将内容存进缓存
*
* @param id
* @return
*/
@RequestMapping("/cache_2")
@Cacheable(value = "test", key = "#id")
public Test Cacheable(@RequestParam("id") int id) {
return testRepository.findOne(id);
}
/**
* 删除缓存
*
* @param id
* @return
*/
@RequestMapping("/cache_3")
@CacheEvict(value = "test")
public Test CacheEvict(@RequestParam("id") int id) {
return testRepository.findOne(id);
}
}
测试缓存的方法是,若缓存中存在该记录,则重新访问一个URL获取同一个数据,则在后台的console中是不会输出SQL语句的,就是不会重新从数据库中获取数据,二是从缓存中直接拿去数据。