SpringBoot--实战开发--整合Spring Data JPA(十一)

一、SpringData简介

Spring Data是一个用于简化数据库访问,并支持云服务的开源框架。其主要目标是使得对数据的访问变得方便快捷。

  1. Spring Data JPA能干什么
      可以极大的简化JPA的写法,可以在几乎不用写实现的情况下,实现对数据的访问和操作。除了CRUD外,还包括如分页、排序等一些常用的功能。
  2. Spring Data JPA 有什么
      主要来看看Spring Data JPA提供的接口,也是Spring Data JPA的核心概念:
        1:Repository:最顶层的接口,是一个空的接口,目的是为了统一所有Repository的类型,且能让组件扫描的时候自动识别。
        2:CrudRepository :是Repository的子接口,提供CRUD的功能
        3:PagingAndSortingRepository:是CrudRepository的子接口,添加分页和排序的功能
        4:JpaRepository:是PagingAndSortingRepository的子接口,增加了一些实用的功能,比如:批量操作等。
        5:JpaSpecificationExecutor:用来做负责查询的接口
        6:Specification:是Spring Data JPA提供的一个查询规范,要做复杂的查询,只需围绕这个规范来设置查询条件即可
  3. 特征
    1)强大的存储库和自定义对象映射抽象
    2)从存储库方法名称中进行动态查询导出
    3)实现域基类提供基本属性
    4)支持透明审核(创建,最后更改)
    5)集成自定义存储库代码的可能性
    6)Easy Spring通过JavaConfig和自定义XML命名空间进行集成
    7)与Spring MVC控制器进行高级集成

二、Maven依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>

三、application.properties配置

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/iotManager?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=1234
#自动建表
spring.jpa.hibernate.ddl-auto=update
#设置数据库方言
spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect
#打印sql
spring.jpa.show-sql=true

Jpa的配置后 jpa.hibernate.ddl-auto= update,在其他低版本的SpringBoot中也有使用spring.jpa.properties.hibernate.hbm2ddl.auto=create-drop 这种配置的,具体根据版本而定。该配置的主要作用是:自动创建、更新、验证数据库结构
1、create:每次加载hibernate时都会删除上一次的生成的表,然后根据你的model类再重新来生成新表,哪怕两次没有任何改变也要这样执行,这就是导致数据库表数据丢失的一个重要原因(一般只会在第一次创建时使用)
2、create-drop:每次加载hibernate时根据model类生成表,但是sessionFactory一关闭,表就自动删除
3、update:最常用的属性,第一次加载hibernate时根据model类会自动建立起表的结构(前提是先建立好数据库),以后加载hibernate时根据model类自动更新表结构,即使表结构改变了但表中的行仍然存在不会删除以前的行。要注意的是当部署到服务器后,表结构是不会被马上建立起来的,是要等应用第一次运行起来后才会
4、validate:每次加载hibernate时,验证创建数据库表结构,只会和数据库中的表进行比较,不会创建新表,但是会插入新值。

四、创建实体

创建一个User类,配置好上面的信息后,启动项目,对应的数据库就会自动生成对应的表结构。@Table、@Entity、@Id等注解是jpa的相关知识。

@Table(name = "t_user")
@Entity
@Data
public class User implements Serializable {
    private static final long serialVersionUID = 1L;
    /**
     * 设为主键 唯一不能为空
     * nullable 是否可以为空
     * unique 唯一
     * @GeneratedValue
     * 就是为一个实体生成一个唯一标识的主键
     * (JPA要求每一个实体Entity,必须有且只有一个主键)
     * @GeneratedValue提供了主键的生成策略。
     * 。@GeneratedValue注解有两个属性,分别是strategy和generator,
     * 其中generator属性的值是一个字符串,默认为"",其声明了主键生成器的名称
     * (对应于同名的主键生成器@SequenceGenerator和@TableGenerator)。
     *  JPA为开发人员提供了四种主键生成策略,其被定义在枚举类GenerationType中,
     *  包括GenerationType.TABLE,GenerationType.SEQUENCE,
     *  GenerationType.IDENTITY和GenerationType.AUTO。
     *  这里生成策略为自增长
     */
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)  
    private Long id;
    @Column(nullable = false, unique = true)
    private String userName;
    @Column(nullable = false)
    private String passWord;
    @Column(nullable = false, unique = true)
    private String email;
    @Column(nullable = true, unique = true)
    private String nickName;
    @Column(nullable = false)
    private String regTime;

五、创建Dao

方法的名称要遵循 findBy + 属性名(首字母大写) + 查询条件(首字母大写 Is Equals)
findByNameLike(String name)
findByName(String name)
findByNameAndAge(String name, Integer age)
findByNameOrAddress(String name) 等...

@Repository
public interface UserRepository  extends JpaRepository<User,Long> {

    /**
     * 根据年纪查询用户
     * @param age
     * @return
     */
    User findByAge(Integer age);

    /**
     * 根据年纪和姓名查询
     * @param name
     * @param age
     * @return
     */
    User findByNameAndAge(String name, Integer age);

    /**
     * 对于复杂查询可以使用@Query 编写sql
     * @param name
     * @return
     */
    @Query("from User u where u.name=:name")
    User findUser(@Param("name") String name);
}

该Dao成继承了JpaRepository接口,指定了需要操作的实体对象和实体对象的主键类型,通过查看JpaRepository接口源码可以看到,里面已经封装了创建(save)、更新(save)、删除(delete)、查询(findAll、findOne)等基本操作的函数,使用起来非常方便了,但是还是会存在一些复杂的sql,spring-data-jpa还提供了一个非常方便的方式,通过实体属性来命名方法,它会根据命名来创建sql查询相关数据,对应更加复杂的语句,还可以用直接写sql来完成。

继承了JpaRepository就相当于有了下面的数据访问操作方法:

@NoRepositoryBean
public interface JpaRepository<T, ID extends Serializable> extends PagingAndSortingRepository<T, ID> {
    List<T> findAll();
    List<T> findAll(Sort var1);
    List<T> findAll(Iterable<ID> var1);
    <S extends T> List<S> save(Iterable<S> var1);
    void flush();
    <S extends T> S saveAndFlush(S var1);
    void deleteInBatch(Iterable<T> var1);
    void deleteAllInBatch();
    T getOne(ID var1);
}

六、单元测试

@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringdataApplicationTests {

    @Autowired
    private UserRepository userRepository;

    /**
     * 新增用户
     * @throws Exception
     */
    @Test
    public void testAddUser() throws Exception {
        User user = new User();
        user.setName("zhangsan");
        user.setAge(12);
        userRepository.save(user);

        User user2 = new User();
        user2.setName("lishi");
        user2.setAge(22);
        userRepository.save(user2);
    }

    /**
     * 删除用户(根据对象删除时,必须要有ID属性)
     * @throws Exception
     */
    @Test
    public void testDelUser() throws Exception {
        User user = new User();
        user.setId(1L);
        user.setName("zhangsan");
        user.setAge(12);
        userRepository.delete(user);
    }

    /**
     * 修改用户信息
     * @throws Exception
     */
    @Test
    public void testUpdUser() throws Exception {
        User user = new User();
        user.setId(2L);
        user.setName("zhangsan11");
        user.setAge(122);
        userRepository.save(user);
    }

    /**
     * 查询用户
     * @throws Exception
     */
    @Test
    public void testQueryUser() throws Exception {
        User user = userRepository.findByAge(22);
        System.out.println(user.getName());

        User user2 = userRepository.findByNameAndAge("lishi", 22);
        System.out.println(user2.getName());

        User user3 = userRepository.findUser("zhangsan11");
        System.out.println(user3.getName());
    }

    /**
     * 查询所有用户
     * @throws Exception
     */
    @Test
    public void testQueryUserList() throws Exception {
        List<User> list = userRepository.findAll();
        for (User user : list) {
            System.out.println(user.getName());
        }
    }
}

如果没有表,将会自动生成。

七、主要接口

CrudReposiroty : 继承了Repository
Crud主要是添加了对数据的增删改查的方法
PagingAndSortingRepository: 继承了CrudRepository
JPARepository: 继承了PagingAndSortingRepository接口
JpaSpecificationExecutor: 这个接口单独存在,没有继承以上说的接口
主要提供了多条件查询的支持,并且可以在查询中添加分页和排序。
因为这个接口单独存在,因此需要配合以上说的接口使用,如:

/**
 * JpaSpecificationExecutor是单独存在的,需要配合这JpaRepository一起使用
 */
@Repository
public interface UserJpaSpecificationExecutor extends JpaSpecificationExecutor<User>, JpaRepository<User, Integer> {
}

八、JPA的查询方法

基于@Query注解的查询和更新:

/**
     * SQL nativeQuery的值是true 执行的时候不用再转化
     * @param name
     * @return
     */
    @Query(value = "SELECT * FROM table_user WHERE name = ?1", nativeQuery = true)
    List<User> findByUsernameSQL(String name);

基于HQL:

 /**
     * 基于HQL
     * @param name
     * @param id
     * @return
     */
    @Query("Update User set name = ?1 WHERE id = ?2")
    @Modifying
    int updateNameAndId(String name, Integer id);

在JPA中有三种方式可以进行数据的查询(1,方法命名查询 2,@NamedQuery查询 3,@Query查询),
假设有一张表叫PERSON,字段:ID(INT),NAME(VARCHAR),AGE(INT),ADDRESS(VARCHAR).
实体类:id(integer),name(String),age(integer),address(String)

  1. 第一种:方法命名查询

1) 使用findBy,And关键字

public interface PersonRepository extends Repository<Person, Integer> {
       /*
    * 通过地址进行查询,参数为address,
    * 相当于JPQL:select p from Person p where p.address=?1
    * */
    List<Person> findByAddress(String address);
       /*
    * 通过地址和名字进行查询,参数为name,address
    * 相当于JPQL:select p from Person p where p.name=?1 and address=?2
    * */
    Person findByNameAndAddress(String name,String address);
}

从代码可以看出,使用findBy,And这样的关键字,其中的findBy可以用find,getBy,query,read来进行代替。
而And就相当于sql语句中的and。

2) 用关键字限制结果数量,用top和first来实现

*
*查询符合条件的前十条记录
*/
List<Person> findFirst10ByName(String name)
/*
*查询符合条件的前30条记录
*/
List<Person> findTop30ByName(String name);
  1. 第二种:@NamedQuery查询
    Spring Data JPA 支持@NameQuery来定义查询方法,即一个名称映射一个查询语句(要在实体类上写,不是接口里写):
@Entity
@NamedQuery(name="Person.findByName",
query="select p from Person p where p.name=?1")
public class Person{
}

这样子就重新定义了findByName这个方法了。
如果要将多个方法都进行重新定义,可以使用@NameQueries标签,示例如下:

@Entity
@NamedQueries({
@NamedQuery(name="Person.findByName",
query="select p from Person p where p.name=?1"),
@NamedQuery(name = "Person.withNameAndAddressNamedQuery",
query = "select p from Person p where p.name=?1 and address=?2")
})
public class Person{

}

这个时候,接口里定义的findByName方法就是上面的方法了,不再是方法命名查询的方法了。

  1. 第三种:@Query查询
    Spring Data JPA 支持@Query来定义查询方法,使用方法是将@Query写在接口的方法上面:
public interface PersonRepository extends Repository<Person, Integer> {
     @Query("select p from Person p where p.name=?1 and p.address=?2")
    Person withNameAndAddressQuery(String name,String address);
   
}

这里的参数是根据索引号来进行查询的。
当然我们也是可以根据名称来进行匹配,然后进行查询的,示例如下:

public interface PersonRepository extends Repository<Person, Integer> {   
@Query("select p from Person p where p.name= :name and p.address= :address")
Person withNameAndAddressQuery(@Param("name")String name,@Param("address")String address);
}

Spring Data JPA支持使用@Modifying和@Query注解组合来进行更新查询,示例如下:

public interface PersonRepository extends Repository<Person, Integer> {

@Modifying
@Transcational
@Query("update Person p set p.name=?1 ")
int setName(String name);
}

int表示的是更新语句所影响的行数。

  1. 排序查询
userRepository.findAll(new Sort(new Sort.Order(Sort.Direction.ASC,"id")));
  1. 分页查询
//分页查询全部,返回封装了分页信息,注意: 0为第一页,1为第2页
Page<User> pageInfo = userRepository.findAll(PageRequest.of(0, 3, Sort.Direction.ASC, "id"));
log.info("总页数" + pageInfo.getTotalPages());
log.info("页大小" + pageInfo.getSize());
log.info("当前页" + pageInfo.getPageable().getPageNumber());
log.info("总记录数" + pageInfo.getTotalElements());
//内容
List<User> userList = pageInfo.getContent();

  1. example查询
User user = new User();
user.setUsername("admin");
Example<User> example = Example.of(user);
List<User> list = userRepository.findAll(example);
log.info(list);
  1. getOne方法
    需添加配置:
#延迟加载
spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true

运行:

# 只可读取存在的数据,不存在抛异常
  User user=userRepository.getOne(1L);
  1. findOne方法
User user=new User();
user.setId(3L);
Example example=Example.of(user);
Optional<User> optionalUser=userRepository.findOne(example);
log.info(optionalUser.get().getUsername());

说明:如果不存在,会报:No value present异常

User user = new User();
user.setId(3L);
Example example = Example.of(user);
Optional<User> optionalUser = userRepository.findOne(example);
//判断是否存在
if (optionalUser.isPresent()) {
    log.info(optionalUser.get().getUsername());
}
或:
//存在即返回, 无则提供默认值
User user = new User();
user.setId(id);
Example<User> userExample = Example.of(user);
return userRepository.findOne(userExample).orElse(null); 

在查询时,通常需要同时根据多个属性进行查询,且查询的条件也格式各样(大于某个值、在某个范围等等),Spring Data JPA 为此提供了一些表达条件查询的关键字,大致如下:
And --- 等价于SQL 中的and 关键字,比如findByUsernameAndPassword(String user, Striang pwd);
Or --- 等价于SQL 中的or 关键字,比如findByUsernameOrAddress(String user, String addr);
Between --- 等价于SQL 中的between 关键字,比如findBySalaryBetween(int max, int min);
LessThan --- 等价于SQL 中的"<",比如findBySalaryLessThan(int max);
lGreaterThan --- 等价于SQL 中的">",比如findBySalaryGreaterThan(int min);
IsNull --- 等价于SQL 中的"is null",比如findByUsernameIsNull();
IsNotNull --- 等价于SQL 中的"is not null",比如findByUsernameIsNotNull();
NotNull --- 与IsNotNull 等价;
Like --- 等价于SQL 中的"like",比如findByUsernameLike(String user);
NotLike --- 等价于SQL 中的"not like",比如findByUsernameNotLike(String user);
OrderBy --- 等价于SQL 中的"order by",比如findByUsernameOrderBySalaryAsc(String user);
Not --- 等价于SQL 中的"! =",比如findByUsernameNot(String user);
In --- 等价于SQL 中的"in",比如findByUsernameIn(Collection<String> userList) ,方法的参数可以
是Collection 类型,也可以是数组或者不定长参数;
NotIn --- 等价于SQL 中的"not in",比如findByUsernameNotIn(Collection<String> userList) ,方法的参数可以是Collection 类型,也可以是数组或者不定长参数;

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,198评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,663评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,985评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,673评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,994评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,399评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,717评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,407评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,112评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,371评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,891评论 1 256
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,255评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,881评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,010评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,764评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,412评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,299评论 2 260

推荐阅读更多精彩内容