Spring Data JPA

Spring Data

Spring Data’s mission is to provide a familiar and consistent, Spring-based programming model for data access while still retaining the special traits of the underlying data store.

常用子项目

  • Spring Data JDBC
  • Spring Data JPA
  • Spring Data MongoDB
  • Spring Data Redis

传统 JDBC

  1. JdbcUtil 工具类
import java.sql.*;

public class JdbcUtil {

    public static Connection getConnection() throws Exception {
        String url = "jdbc:mysql://localhost/db_springboot?useSSL=false";
        String user = "root";
        String password = "123456";
        String driverClass = "com.mysql.jdbc.Driver";
        Class.forName(driverClass);
        return DriverManager.getConnection(url, user, password);
    }

    public static void release(ResultSet resultSet, Statement statement,Connection connection) {
        if (resultSet != null) {
            try {
                resultSet.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

        if (statement != null) {
            try {
                statement.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

        if (connection != null) {
            try {
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}
  1. DAO 接口与实现
import java.util.List;

public interface UserDAO {
    List<User> query();
}
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;

public class UserDAOImpl implements UserDAO {

    @Override
    public List<User> query() {
        List<User> users = new ArrayList<>();

        Connection connection = null;
        PreparedStatement statement = null;
        ResultSet resultSet = null;
        String sql = "select * from user";

        try {
            connection = JdbcUtil.getConnection();
            statement = connection.prepareStatement(sql);
            resultSet = statement.executeQuery();

            User user = null;
            while (resultSet.next()) {
                int id = resultSet.getInt("id");
                String name = resultSet.getString("name");
                int age = resultSet.getInt("age");

                user = new User();
                user.setId(id);
                user.setName(name);
                user.setAge(age);
                users.add(user);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JdbcUtil.release(resultSet, statement, connection);
        }

        return users;
    }
}
  1. Test
import org.junit.Assert;
import org.junit.Test;

public class JdbcUtilTest {
    @Test
    public void getConnection() throws Exception {
        Assert.assertNotNull(JdbcUtil.getConnection());
    }
}

Spring JDBC

  1. application.yml 配置 DataSource
spring:
  datasource:
    url: jdbc:mysql://localhost/db_springboot?useSSL=false
    username: root
    password: 123456
    driver-class-name: com.mysql.jdbc.Driver
  1. DAO 层注入 JdbcTemplate
import org.springframework.jdbc.core.JdbcTemplate;

import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;

@Repository
public class UserDAOImpl implements UserDAO {

    private final JdbcTemplate jdbcTemplate;

    public UserDAOImpl(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    @Override
    public List<User> query() {
        final List<User> users = new ArrayList<>();

        String sql = "select * from user";
        jdbcTemplate.query(sql, (ResultSet rs) -> {
            int id = rs.getInt("id");
            String name = rs.getString("name");
            int age = rs.getInt("age");
            User user = new User();
            user.setId(id);
            user.setName(name);
            user.setAge(age);
            users.add(user);
        });

        return users;
    }
}
  1. 测试类
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class UserDAOImplTest {

    @Autowired
    private UserDAO userDAO;

    @Test
    public void query() {
        List<User> users = userDAO.query();
        users.forEach(System.out::println);
    }
}

Spring Data JPA

  1. User 使用注解可以自动创建表
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;

@Getter
@Setter
@ToString
@Entity
public class User {

    @Id
    @GeneratedValue
    private Integer id;

    @Column(length = 10)
    @NotEmpty
    private String name;

    @NotNull
    private Integer age;
}
  1. UserRepository
import org.springframework.data.repository.Repository;

public interface UserRepository extends Repository<User, Integer> {
    User findByName(String name);
}
  1. Test
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class UserRepositoryTest {

    @Autowired
    private UserRepository userRepository;

    @Test
    public void findByName() {
        System.out.println(userRepository.getfindName("LeifChen"));
    }
}

Repository 接口

  • public interface Repository {}

Repository 接口是 Spring Data 的核心接口,不提供任何实现方法。是一个空接口,也称为标记接口。

(1) CrudRepository:继承 Repository,实现了 CRUD 相关方法。

  • S save(S entity); 保存实体
  • Iterable saveAll(Iterable entities); 保存实体集合
  • Optional findById(ID id); 根据 id 查询实体
  • boolean existsById(ID id); 返回是否存在对应 id 的实体
  • Iterable findAll(); 返回所有实体
  • Iterable findAllById(Iterable ids); 根据 ids 查询实体
  • long count(); 返回实体数
  • void deleteById(ID id); 根据 id 删除实体
  • void delete(T entity); 根据实体删除
  • void deleteAll(Iterable entities); 根据实体列表删除
  • void deleteAll(); 删除所有实体

(2) PagingAndSortingRepository:继承 CurdRepository,实现了分页排序相关方法

  • Iterable findAll(Sort sort); 查询所有实体并按照 sort 排序
  • Page findAll(Pageable pageable); 分页查询

测试代码:

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class UserRepositoryTest {

    @Autowired
    private UserRepository userRepository;

    @Test
    public void pageAndSort() {
        Pageable pageable = PageRequest.of(0, 5, Sort.Direction.DESC, "age");
        Page<User> page = userRepository.findAll(pageable);

        System.out.println("查询的总页数:" + page.getTotalPages());
        System.out.println("查询的总记录数:" + page.getTotalElements());
        System.out.println("查询的当前第几页:" + (page.getNumber() + 1));
        System.out.println("查询的当前页面的集合:" + page.getContent());
        System.out.println("查询的当前页面的记录数:" + page.getNumberOfElements());
    }
}

(3) JpaRepository:继承 PagingAndSortingRepository,实现了 JPA 规范相关方法

测试代码:

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class UserRepositoryTest {

    @Autowired
    private UserRepository userRepository;

    @Test
    public void find() {
        userRepository.findById(3).ifPresent(System.out::println);
    }

    @Test
    public void exist() {
        System.out.println("user(3):" + userRepository.existsById(3));
        System.out.println("user(100):" + userRepository.existsById(100));
    }
}

(4) JpaSpecificationExecutor:用来实现 JPA 的动态复杂条件查询

测试代码:

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import javax.persistence.criteria.Path;

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class UserRepositoryTest {

    @Autowired
    private UserRepository userRepository;

    @SuppressWarnings("unchecked")
    @Test
    public void complexQuery() {
        Pageable pageable = PageRequest.of(0, 5, Sort.Direction.ASC, "age");
        Specification<User> specification = (Specification<User>) (root, query, criteriaBuilder) -> {
            Path path = root.get("age");
            return criteriaBuilder.gt(path, 30);
        };
        Page<User> page = userRepository.findAll(specification, pageable);

        System.out.println("查询的总页数:" + page.getTotalPages());
        System.out.println("查询的总记录数:" + page.getTotalElements());
        System.out.println("查询的当前第几页:" + (page.getNumber() + 1));
        System.out.println("查询的当前页面的集合:" + page.getContent());
        System.out.println("查询的当前页面的记录数:" + page.getNumberOfElements());
    }
}

自定义 Repository 接口的两种方法:

  • (1) 继承 Repository 接口
import com.chen.model.User;
import org.springframework.data.repository.Repository;

public interface UserRepository extends Repository<User, Integer> {
    User findByName(String name);
}

  • (2) 注解 @RepositoryDefinition
import org.springframework.data.repository.RepositoryDefinition;

@RepositoryDefinition(domainClass = User.class, idClass = Integer.class)
public interface UserRepository {
    User findByName(String name);
}

JPA 方法关键字定义规则

参考官方文档

Keyword Sample JPQL snippet
And findByNameAndAge … where x.name = ?1 and x.age = ?2
Or findByNameOrAge … where x.name = ?1 or x.age = ?2
Is,Equals findByName,findByNameIs,findByNameEquals … where x.name = ?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 is not null
Like findByNameLike … where x.name like ?1
NotLike findByNameNotLike … where x.name not like ?1
StartingWith findByNameStartingWith … where x.name like ?1 (parameter bound with appended %)
EndingWith findByNameEndingWith … where x.name like ?1 (parameter bound with prepended %)
Containing findByNameContaining … where x.name like ?1 (parameter bound wrapped in %)
OrderBy findByNameOrderByAgeDesc … where x.name = ?1 order by x.age desc
Not findByNameNot … where x.lastname <> ?1
In findByAgeIn(Collection ages) … where x.age in ?1
NotIn findByAgeNotIn(Collection ages) … where x.age not in ?1
True findByActiveTrue() … where x.active = true
False findByActiveFalse() … where x.active = false
IgnoreCase findByNameIgnoreCase … where UPPER(x.name) = UPPER(?1)

其中 find 关键字也可以为 get (例如:getByName) ,使用关键字命名方法会导致名称特别长,并且很难实现复杂查询,因此还可以通过注解的方式扩展。

注解

  • @Query:查询
  • @Modifying:更新、删除
  • @Transactional:事务,一般在 Service 层

参考代码:

  1. UserRepository
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.query.Param;

import java.util.List;

public interface UserRepository extends Repository<User, Integer> {

    /**
     * 根据名称查询用户(使用 JPA 命名规则)
     *
     * @param name 名称
     * @return
     */
    User findByName(String name);

    /** 
    * 查询 id 为最大值的用户 
    * 
    * @return 
    */
    @Query("select u from #{#entityName} u where id=(select max(id) from User t1)")
    User findByMaxId();

    /**
     * 根据名称、年龄查询用户(使用 @Query 注解及占位符 ? 绑定参数)
     *
     * @param name 名称
     * @param age  年龄
     * @return
     */
    @Query("select u from #{#entityName} u where u.name like ?1% and u.age >= ?2")
    List<User> findByParam(String name, Integer age);

    /**
     * 根据名称、年龄查询用户(使用 @Query 注解及 @Param 绑定参数)
     *
     * @param name 名称
     * @param age  年龄
     * @return
     */
    @Query("select u from #{#entityName} u where u.name like :name% and u.age >= :age")
    List<User> findByParam2(@Param("name") String name, @Param("age") Integer age);

    /**
     * 查询用户数
     *
     * @return
     */
    @Query(nativeQuery = true, value = "select count(1) from user")
    long count();

    /**
     * 根据 id 更新用户年龄
     *
     * @param id
     * @param age 年龄
     */
    @Modifying
    @Query("update User u set u.age = :age where u.id = :id")
    void updateById(@Param("id") Integer id, @Param("age") Integer age);

    /**
     * 根据 id 删除用户
     *
     * @param id
     */
    @Modifying
    @Query("delete from User u where u.id = :id")
    void deleteById(@Param("id") Integer id);
}
  1. UserRepositoryTest
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class UserRepositoryTest {

    @Autowired
    private UserRepository userRepository;

    @Test
    public void findByName() {
        System.out.println(userRepository.findByName("LeifChen"));
    }

    @Test
    public void findByMaxId() {
        System.out.println(userRepository.findByMaxId());
    }

    @Test
    public void findByParam() {
        System.out.println(userRepository.findByParam("Test", 20));
    }

    @Test
    public void findByParam2() {
        System.out.println(userRepository.findByParam2("Test", 20));
    }

    @Test
    public void count() {
        System.out.println(userRepository.count());
    }
}
  1. UserService
import com.chen.repository.UserRepository;
import org.springframework.stereotype.Service;

import javax.transaction.Transactional;

@Service
public class UserService {

    private final UserRepository userRepository;

    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Transactional(rollbackOn = Exception.class)
    public void updateById(Integer id, Integer age) {
        userRepository.updateById(id, age);
    }

    @Transactional(rollbackOn = Exception.class)
    public void deleteById(Integer id) {
        userRepository.deleteById(id);
    }
}
  1. UserServiceTest
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class UserServiceTest {

    @Autowired
    private UserService userService;

    @Test
    public void updateById() {
        userService.updateById(3, 28);
    }

    @Test
    public void deleteById() {
        userService.deleteById(3);
    }
}

比较

  • JDBC:代码冗余,需要手写 SQL 语句,需要手工关闭连接,每次获取连接和关闭连接会加大数据库的负担。
  • Spring JDBC:通过 Spring 管理连接池,需要手写 SQL 语句,手动映射字段关系。
  • Spring Data JPA:通过 JPA 规范命名方法,不需要手写 SQL 语句。

参考

  1. 轻松愉快之玩转SpringData
  2. GitHub
  3. Spring-Data-JPA

你可能感兴趣的:(【Spring】)