jpa查询的使用

jpa的使用
1.引入pom依赖

<!--springboot-JPA-->
<dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!--mysql连接-->
<dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
</dependency>

2.配置数据源

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/joe?serverTimezone=UTC
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver
  jpa:
    show-sql: true

2.1 driver-class-name(驱动类)
根据mysql版本不同不一样,有的是com.mysql.cj.jdbc.Driver,有的是com.mysql.jdbc.Driver,不会报错,但是会有提示信息。
提示:Loading class com.mysql.jdbc.Driver’. This is deprecated. The new driver class is com.mysql.cj.jdbc.Driver’.
2.2 time zone 异常
异常:java.sql.SQLException: The server time zone value ‘Öйú±ê׼ʱ¼ä’ is unrecognized or represents more than one time zone.
解决:在datasource-url后拼接参数 serverTimezone=UTC

3.生成数据库表实体类domain

@Data
@Entity
@Table(name = "user")
public class User implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "user_id")
    private Long userId;

    @Column(name = "user_name")
    private String userName;

    private Long age;

    private String gender;

    private String address;
}

3.1 实体类建议用工具生成,因为jpa要求实体类中的字段都必须在数据库中找到对应列,即类属性只能比表字段少不能多,否则会报异常:
org.springframework.dao.InvalidDataAccessResourceUsageException

如何生成 IDEA自动生成JPA实体

3.2 数据库所有的表必须要有主键,jpa要求所有的表都必须有主键列,实体类必须有@Id标注,可以是联合主键,但是不能没有,没有的话会报异常:
org.hibernate.AnnotationException: No identifier specified for entity: com.joe.jpa.domain.User
联合主键如何使用 JPA联合主键

4.编写dao层
@Repository
public interface UserRepository extends JpaRepository<User, Integer> {
}
1.jpa已经给我们提供了单表的增删改查操作,只需要在dao接口上实现 JpaRepository<T, ID>接口,就可以通过接口里的方法完成crud操作。
其中JpaRepository里的泛型,T是表的实体类,ID是表的主键对应的实体类属性数据类型,我这里是<User, Integer>。

5.测试类
即使自定义的UserRepository没有编写任何代码,注入后一样可以能使用 save(),findXXX(),delete(),count()等方法,并且测试有效,其实是JPA内部 CrudRepository 提供, SimpleJpaRepository 实现的,而 JpaRepository 是CrudRepository的子类,我们又实现了 JpaRepository所以就可以直接用。

详细类图在 JpaRepository 中 右键>Diagrams>show Diagrams…>java class Diagrams 可以查看。 图文步骤:IDEA查看类继承关系

5.1 保存

save(S var1)

@Test
public void testSave() {

    User user = new User();
    user.setUserName("张三");
    user.setAge(23);
    user.setGender("男");
    userRepository.save(user);
    log.info("保存成功,主键:{}", user.getUserId());

}

5.2 查询
查询全部

List<T> findAll();

    @Test
    public void testFindAll() {

        List<User> userList = userRepository.findAll();
        log.info("查询所有:{}", userList);
    }
主键查询
Optional<T> findById(ID var1);

List<T> findAllById(Iterable<ID> var1);

    @Test
    public void testFindById() {
        //主键查询-查询一个
        Optional<User> userOptional = userRepository.findById(2);
        if (userOptional.isPresent()) {
            log.info("根据主键查询:{}", userOptional.get());
        }

        //主键查询-查询多个
        List<Integer> userIdList = Arrays.asList(new Integer[]{2, 3});
        List<User> userListByIds = userRepository.findAllById(userIdList);
        log.info("根据多个主键查询:{}", userListByIds);
    }
    
5.3 更新
```java
save(S var1)

    @Test
    public void testUpdate() {
        User user = new User();
        user.setUserId(2);
        user.setUserName("李十四");
        userRepository.save(user);
        log.info("保存成功");
    } 
//添加和更新操作都是save(T t)方法,逻辑是根据主键判断的,如果数据库中有数据能匹配到参数中的主键,就更新匹配到的数据,否则就新添加。

5.4 删除
void delete(T var1)
void deleteById(ID var1);
void deleteAll();

@Test
public void testDelete(){
    //删除名字是张三的记录
    User user = new User();
    user.setUserName("张三");
    userRepository.delete(user);
    
    //删除主键是2的记录(李四)
    Integer userId = 2;
    userRepository.deleteById(userId);
}
5.5计数
long count();

@Test
public void testCount() {
    long count = userRepository.count();
    System.out.println(count);
}

批量插入
方法: List saveAll(Iterable var1);
实例:

   @Test
    public void testSaveAll() {
        User user2 = new User();
        user2.setUserName("李四");
        user2.setAge(27);
        user2.setGender("男");

        User user3 = new User();
        user3.setUserName("王五");
        user3.setAge(25);
        user3.setGender("女");

        User user4 = new User();
        user4.setUserName("赵六");
        user4.setAge(26);
        user4.setGender("女");

        List<User> list = new ArrayList();
        list.add(user2);
        list.add(user3);
        list.add(user4);
        userRepository.saveAll(list);
        log.info("批量保存成功");
    }

排序
方法: List findAll(Sort var1);
实例:

@Test
public void testFindSort() {

    Sort ageSort = Sort.by("age");//根据年龄升序
    Sort ageSort1 = Sort.by(Sort.Direction.ASC, "age");//根据年龄升序
    Sort ageSort2 = Sort.by(Sort.Direction.DESC, "age");//根据年龄倒叙
    Sort ageSort3 = Sort.by(Sort.Direction.DESC, "age","name");//根据名字和年龄倒叙
    
    List<User> userListSortByAge = userRepository.findAll(ageSort);
    log.info("查询所有按照age倒叙:{}", userListSortByAge);
}

说明:JPA的排序是借助 Sort 类实现的,Sort类有很多种办法创建,官方给出的是通过 Sort.by(Sort.Direction direction, String… properties)实例化
其中第一个参数是排序策略,可省略,缺省升序(Sort.Direction.ASC),从第二个参数开始是排序的字段,可以一个或多个。

条件查询
方法: List findAll(Example var1);
实例:

@Test
public void testFindByExample() {

    //等同条件查询
    User user = new User();
    user.setGender("男");
    Example<User> userExample = Example.of(user);
    List<User> userExampleList = userRepository.findAll(userExample);
    log.info("性别为男的用户为:{}", userExampleList);

    //等同条件查询唯一记录,如果查到两条会报错
    User user2 = new User();
    user2.setUserName("张三");
    Example<User> oneUserExample = Example.of(user2);
    Optional<User> oneUser = userRepository.findOne(oneUserExample);
    log.info("名字叫张三的记录:{}", oneUser);
}

说明:JPA的条件查询是借助 Example 类实现的,Example通过 Example.of(T probe)实例化,参数是条件实体。条件都是等值条件,
比如name = ‘张三’ ,age = ‘23’ ,如果需要 age > ‘23’ 或者 like(模糊匹配),in, not in ,is null 等条件的查询,Example 不够用,需要借助更详细的
查询条件类 Predicate 实现,这个放到下一篇在写。

分页查询
方法: Page findAll(Pageable var1);
实例:

@Test
public void testFindByPage() {
    //分页查询  jpa页码是从0开始的,传入1的话,返回的是第二页的数据
    PageRequest pageParam = PageRequest.of(1, 2);
    Page<User> userListByPage = userRepository.findAll(pageParam);
    long totalElements = userListByPage.getTotalElements();
    int totalPages = userListByPage.getTotalPages();
    List<User> content = userListByPage.getContent();
    log.info("分页查询结果,总记录数:{},总页数:{},选定页数据:{}", totalElements, totalPages, content);
}

多条件查询有很多方式
接口命名策略
按照JPA的命名策略命名,就可以实现单条件或者多条件的等值查询
命名策略 要以find或者findAll开头,单字段查询,find后跟字段的属性名字,参数传入字段的属性类型,多个条件中间用and区分开。
实例:
接口:
Order findByExpressNo(String expressNo);
List findByUserId(Integer userId);
List findByUserNameAndUserAddress(String userName, String userAddress);
测试类:

@Test
public void testFindByXXX() {
    //按照订单号查询
    Order order = orderRepository.findByExpressNo("201802215678");
    log.info("按照订单号查询结果:{}", order);
    //按照用户编号查询
    List<Order> orderList = orderRepository.findByUserId(2);
    log.info("按照用户编号查询订单:{}", orderList);
    //按照收货人和收货地址查询
    List<Order> orderList2 = orderRepository.findByUserNameAndUserAddress("张无忌", "光明顶");
    log.info("按照收货人和收货地址查询订单:{}", orderList2);
}

说明:
1).后缀跟的是字段名对应的Java类属性名,不是字段名。
2).定义接口的返回值,要区分开始返回一条记录还是多条记录。如果返回多条记录,用一个记录去接收,会报错。
Predicate构建查询条件
方法:
List findAll(@Nullable Specification var1);
Page findAll(@Nullable Specification var1, Pageable var2);
实例:

@Test
public void testPredicate(){
    Pageable page = PageRequest.of(0, 2, Sort.by(Sort.Direction.DESC, "orderDate"));//按照订单日期倒叙,查询第一页,每页两条数据
    Specification<Order> userSpecification = new Specification<Order>() {
        @Override
        public Predicate toPredicate(Root root, CriteriaQuery criteriaQuery, CriteriaBuilder criteriaBuilder) {
            List<Predicate> list = new ArrayList<>();//查询条件集 
            list.add(criteriaBuilder.like(root.get("userName").as(String.class),  "张" + "%"));//收货人以张开头   like
            list.add(criteriaBuilder.equal(root.get("orderStatus").as(String.class), "已完成"));//订单状态为已完成  =
            list.add(criteriaBuilder.ge(root.get("orderAmount").as(BigDecimal.class), new BigDecimal("100")));//订单金额大于100  >
            list.add(criteriaBuilder.isNotNull(root.get("userAddress").as(String.class)));//收货地址不为空  is not null
            return criteriaBuilder.and(list.toArray(new Predicate[list.size()]));
        }
    };

    Page<Order> all = orderRepository.findAll(userSpecification, page);
    log.info("条件查询结果:{}",all.getContent());
}

说明:
主要借助CriteriaBuilder和Predicate两个类完成拼接条件,
CriteriaBuilder 提供了 .and(且) .or(或) .like(模糊) .equals(=) isNotNull(非空) .ge(大于) 等方法来构造查询条件,具体使用参见实例。
自定义SQL
JPA同样允许自己写SQL操作记录。
实例:
接口:

@Query(value = "select * from order_info where user_name = ?1 and user_address = ?2", nativeQuery = true)
List<Order> selectByNameAndAddress(String name, String address);
@Modifying
@Transactional
@Query(value = "update order_info set user_address = ?1 where order_id = ?2", nativeQuery = true)
void updateAddressByOrderId(String address, Integer orderId);

测试:

@Test
public void testBySQL() {
    //根据名字和地址查询
    List<Order> orders = orderRepository.selectByNameAndAddress("张无忌", "光明顶");
    log.info("自定义SQL根据名字和地址查询:{}", orders);
    //修改订单地址
    orderRepository.updateAddressByOrderId("武当山2114", 1);
}

说明:
1). 自定义SQL可以随便命名方法名
2).需要用@Query注解,如果是更新删除操作还需要有 @Modifying和@Transactional 注解
3). SQL里的?表示的占位符,编译和执行的时候,取值是取接口的入参,?后的数字是第几个参数,从0开始

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值