SpringBoot学习(五)操作数据库Spring-Data-JPA

一、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定义函数名需要遵循下列规定:

KeywordSampleSQL
AndfindByLastnameAndFirstnamewhere x.lastname = ?1 and x.firstname = ?2
OrfindByLastnameOrFirstnamewhere x.lastname = ?1 or x.firstname = ?2
Is,EqualsfindByFirstname
findByFirstnameIs
findByFirstnameEquals
where x.firstname = 1?
BetweenfindByStartDateBetweenwhere x.startDate between 1? and ?2
LessThanfindByAgeLessThanwhere x.age < ?1
LessThanEqualfindByAgeLessThanEqualwhere x.age <= ?1
GreaterThanfindByAgeGreaterThanwhere x.age > ?1
GreaterThanEqualfindByAgeGreaterThanEqualwhere x.age >= ?1
AfterfindByStartDateAfterwhere x.startDate > ?1
BeforefindByStartDateBeforewhere x.startDate < ?1
IsNullfindByAgeIsNullwhere x.age is null
IsNotNull,NotNullfindByAge(Is)NotNullwhere x.age not null
LikefindByFirstnameLikewhere x.firstname like ?1
NotLikefindByFirstnameNotLikewhere x.firstname not like ?1
StartingWithfindByFirstnameStartingWithwhere x.firstname like ?1 (parameter bound with appended %)
EndingWithfindByFirstnameEndingWithwhere x.firstname like ?1 (parameter bound with prepended %)
ContainingfindByFirstnameContainingwhere x.firstname like ?1 (parameter bound wrapped in %)
OrderByfindByAgeOrderByLastnameDescwhere x.age = ?1 order by x.lastname desc
NotfindByLastnameNotwhere x.lastname <> ?1
InfindByAgeIn(Collection ages)where x.age in ?1
NotInfindByAgeNotIn(Collection age)where x.age not in ?1
TruefindByActiveTrue()where x.active = true
FalsefindByActiveFalse()where x.active = false
IgnoreCasefindByFirstnameIgnoreCasewhere 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
timeouttimeout指定事务过期时间,默认为当前数据库的事务过期时间
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语句的,就是不会重新从数据库中获取数据,二是从缓存中直接拿去数据。

这是我的源码,有兴趣的可以下载看看http://download.csdn.net/download/q15150676766/9924308

  • 0
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值