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"));
    }
}

Reposiroty 接口

  • public interface Repository<T, ID> {}

Reposiroty 接口是 Spring Data 的核心接口,不提供任何實現方法。是一個空接口,也稱爲標記接口。

(1) CrudRepository:繼承 Repository,實現了 CRUD 相關方法。

  • <S extends T> S save(S entity); 保存實體
  • <S extends T> Iterable<S> saveAll(Iterable<S> entities); 保存實體集合
  • Optional<T> findById(ID id); 根據 id 查詢實體
  • boolean existsById(ID id); 返回是否存在對應 id 的實體
  • Iterable<T> findAll(); 返回所有實體
  • Iterable<T> findAllById(Iterable<ID> ids); 根據 ids 查詢實體
  • long count(); 返回實體數
  • void deleteById(ID id); 根據 id 刪除實體
  • void delete(T entity); 根據實體刪除
  • void deleteAll(Iterable<? extends T> entities); 根據實體列表刪除
  • void deleteAll(); 刪除所有實體

(2) PagingAndSortingRepository:繼承 CurdRepository,實現了分頁排序相關方法

  • Iterable<T> findAll(Sort sort); 查詢所有實體並按照 sort 排序
  • Page<T> 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());
    }
}

自定義 Reposiroty 接口的兩種方法:

  • (1) 繼承 Reposiroty 接口
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<Age> ages) … where x.age in ?1
NotIn findByAgeNotIn(Collection<Age> 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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章