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
- 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();
}
}
}
}
- 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;
}
}
- Test
import org.junit.Assert;
import org.junit.Test;
public class JdbcUtilTest {
@Test
public void getConnection() throws Exception {
Assert.assertNotNull(JdbcUtil.getConnection());
}
}
Spring JDBC
- application.yml 配置 DataSource
spring:
datasource:
url: jdbc:mysql://localhost/db_springboot?useSSL=false
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
- 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;
}
}
- 測試類
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
- 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;
}
- UserRepository
import org.springframework.data.repository.Repository;
public interface UserRepository extends Repository<User, Integer> {
User findByName(String name);
}
- 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 層
參考代碼:
- 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);
}
- 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());
}
}
- 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);
}
}
- 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 語句。