SpringBoot24 SpringDataJPA环境搭建、实体类注解、关联查询
1 版本说明
JDK:1.8
MAVEN:3.5
SpringBoot:2.0.4
IDEA:旗舰版207.2
MySQL:5.5
2 SpringDataJPA环境搭建(SpringBoot版本)
2.1 创建一个SrpingBoot项目
需要引入的依赖如下图所示
2.2 配置数据库相关
》创建一个mysql数据库testdemo
》在testdemo中创建一个student表
/* Navicat MySQL Data Transfer Source Server : mysql5.4 Source Server Version : 50540 Source Host : localhost:3306 Source Database : testdemo Target Server Type : MYSQL Target Server Version : 50540 File Encoding : 65001 Date: 2018-08-13 21:13:51 */ SET FOREIGN_KEY_CHECKS=0; -- ---------------------------- -- Table structure for `student` -- ---------------------------- DROP TABLE IF EXISTS `student`; CREATE TABLE `student` ( `id` int(50) NOT NULL AUTO_INCREMENT, `name` varchar(20) NOT NULL, `age` int(10) NOT NULL, `address` varchar(50) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=20 DEFAULT CHARSET=utf8mb4;
》在springboot项目中配置数据库信息
spring: datasource: url: jdbc:mysql://127.0.0.1/testdemo?characterEncoding=utf-8&useSSL=false username: root password: 182838 jpa: properties: hibernate: format_sql: true show_sql: true
2.2.1 利用IDEA连接数据
2.2.2 利用IDEA自动生成实体类
package cn.xiangxu.jpa_demo03.domain.domain_do; import javax.persistence.*; /** * @author 王杨帅 * @create 2018-08-13 21:36 * @desc **/ @Entity @Table(name = "student", schema = "testdemo", catalog = "") public class StudentDO { private int id; private String name; private int age; private String address; @Id @Column(name = "id") public int getId() { return id; } public void setId(int id) { this.id = id; } @Basic @Column(name = "name") public String getName() { return name; } public void setName(String name) { this.name = name; } @Basic @Column(name = "age") public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Basic @Column(name = "address") public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } @Override public boolean equals(Object object) { if (this == object) return true; if (object == null || getClass() != object.getClass()) return false; StudentDO studentDO = (StudentDO) object; if (id != studentDO.id) return false; if (age != studentDO.age) return false; if (name != null ? !name.equals(studentDO.name) : studentDO.name != null) return false; if (address != null ? !address.equals(studentDO.address) : studentDO.address != null) return false; return true; } @Override public int hashCode() { int result = id; result = 31 * result + (name != null ? name.hashCode() : 0); result = 31 * result + age; result = 31 * result + (address != null ? address.hashCode() : 0); return result; } }
2.3 创建持久层
技巧01:持久层接口只需要继承 JpaRepository 接口即可
package cn.xiangxu.jpa_demo03.domain.repository; import cn.xiangxu.jpa_demo03.domain.domain_do.StudentDO; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; /** * @author 王杨帅 * @create 2018-08-13 21:53 * @desc 学生持久层接口 **/ public interface StudentRepository extends JpaRepository<StudentDO, Integer> { }
技巧02:需要使用持久层实例时,只需要进行依赖注入即可
技巧03:持久层接口上不用写 @Repository ,因为继承了 JpaRepository 接口后就已经是一个被Spring容器管理的持久层Bean啦
技巧04:Repository相关接口
2.4 测试持久层
技巧01:继承 JpaRepository 接口后就可以有很过方法可以使用
技巧02:可以直接使用的原因是:拦截 + JDK动态代理
package cn.xiangxu.jpa_demo03.domain.repository; import cn.xiangxu.jpa_demo03.domain.domain_do.StudentDO; import lombok.extern.slf4j.Slf4j; 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.SpringRunner; import java.util.List; import static org.junit.Assert.*; @RunWith(SpringRunner.class) @SpringBootTest @Slf4j public class StudentRepositoryTest { @Autowired private StudentRepository studentRepository; @Test public void findall() { List<StudentDO> all = studentRepository.findAll(); System.out.println(all); } }
技巧03:jpa支持自定义SQL【借助@Query注解实现】
3 Projections 对查询结果的扩展
3.1 返回类型建模
Spring JPA 对 Projections 的扩展的支持,个人觉得这是个非常好的东西,从字面意思上理解就是映射,指的是和 DB 的查询结果的字段映射关系。一般情况下,我们是返回的字段和 DB 的查询结果的字段是一一对应的,但有的时候,需要返回一些指定的字段,不需要全部返回,或者返回一些复合型的字段,还得自己写逻辑。Spring Data 正是考虑到了这一点,允许对专用返回类型进行建模,以便更有选择地将部分视图对象。(声明:来自gitChat)
3.1.1 需求
只需要查询学生的姓名和年龄信息,其余信息不进行查询
3.1.2 思路
声明一个接口,包含我们要返回的属性的方法即可
3.1.3 编程实现
》根据实体类中属性的get方法创建接口
技巧01:接口中的方法就是需要获取的字段在实体类中对应的get方法
package cn.xiangxu.jpa_demo03.domain.domain_do; /** * @author 王杨帅 * @create 2018-08-13 22:16 * @desc **/ public interface StudentBaseInfo { String getName(); Integer getAge(); }
》修改持久层接口中的方法
技巧01:返回值用StudentBaseInfo类型
package cn.xiangxu.jpa_demo02.repository; import cn.xiangxu.jpa_demo02.domain.domain_do.StudentBaseInfo; import cn.xiangxu.jpa_demo02.domain.domain_do.StudentDO; import org.springframework.data.jpa.repository.JpaRepository; import java.util.List; /** * @author 王杨帅 * @create 2018-08-13 10:00 * @desc **/ public interface StudentRepository extends JpaRepository<StudentDO, Integer> { /** * 根据姓名和年龄 * @param name 姓名 * @param age 年龄 * @return */ List<StudentDO> findByNameAndAge(String name, Integer age); /** * 根据年龄段查询【PS: 开区间】 * @param start 最小值 * @param end 最大值 * @return */ List<StudentDO> findByAgeBetween(Integer start, Integer end); /** * 小于给定年龄【PS: 开区间】 * @param age 年龄 * @return */ List<StudentDO> findByAgeLessThan(Integer age); /** * 小于给定年龄【PS: 闭区间】 * @param age 年龄 * @return */ List<StudentDO> findByAgeLessThanEqual(Integer age); List<StudentDO> findByAgeGreaterThan(Integer age); List<StudentDO> findByAgeGreaterThanEqual(Integer age); List<StudentDO> findByName(String name); List<StudentDO> findByNameEquals(String name); List<StudentDO> findByNameIs(String name); List<StudentDO> findByAgeAfter(Integer age); List<StudentDO> findByAgeBefore(Integer age); List<StudentDO> findByAgeAfterOrAgeEquals(Integer age, Integer age2); // List<StudentDO> findByAddress(String address); List<StudentBaseInfo> findByAddress(String address); }
》测试类
package cn.xiangxu.jpa_demo03.domain.repository; import cn.xiangxu.jpa_demo03.domain.domain_do.StudentBaseInfo; import cn.xiangxu.jpa_demo03.domain.domain_do.StudentDO; import cn.xiangxu.jpa_demo03.repository.StudentRepository; import lombok.extern.slf4j.Slf4j; 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.SpringRunner; import java.util.List; @RunWith(SpringRunner.class) @SpringBootTest @Slf4j public class StudentRepositoryTest { @Autowired private StudentRepository studentRepository; @Test public void findall() { List<StudentDO> all = studentRepository.findAll(); System.out.println(all); } @Test public void findByName() { List<StudentBaseInfo> byNameIs = studentRepository.findByNameIs("杨玉林"); System.out.println(byNameIs); } }
》跳坑01:通过这种方式获取到的数据是一个Map对象,如何转化成一个实体类列表呢
》填坑01:创建一个实体类,这个实体类的字段要和创建的接口中方法对应实体类类的字段名保持一致【PS:本博文就继续使用之前的实体类】,将查询到的数据转化成流,然后利用peek这个中间操作进行转化
package cn.xiangxu.jpa_demo03.domain.repository; import cn.xiangxu.jpa_demo03.domain.domain_do.StudentBaseInfo; import cn.xiangxu.jpa_demo03.domain.domain_do.StudentDO; import cn.xiangxu.jpa_demo03.repository.StudentRepository; import lombok.extern.slf4j.Slf4j; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.util.ArrayList; import java.util.List; @RunWith(SpringRunner.class) @SpringBootTest @Slf4j public class StudentRepositoryTest { @Autowired private StudentRepository studentRepository; @Test public void findall() { List<StudentDO> all = studentRepository.findAll(); System.out.println(all); } @Test public void findByName() { List<StudentBaseInfo> byNameIs = studentRepository.findByNameIs("杨玉林"); System.out.println(byNameIs); } @Test public void findBYName02() { List<StudentBaseInfo> byNameIs = studentRepository.findByNameIs("王杨帅"); List<StudentDO> studentDOList = new ArrayList<>(); byNameIs.stream() .peek( i -> { StudentDO studentDO = new StudentDO(); BeanUtils.copyProperties(i, studentDO); studentDOList.add(studentDO); } ) .forEach( System.out::println ); System.out.println(studentDOList); } }
》转化后的结果为
4 实体类注解
待更新...... 2018年8月14日20:21:24
5 关联查询
说明:本博是利用原生的SQL进行关联查询
5.1 需求
现有两张数据库表:
product -> 存放产品信息
provider -> 存放供应商信息
product 和 provider 的关系时多对一的关系,即:一个供应商可能和多个产品对应
5.2 思路
知己利用SQL的关联查询实现
5.3 编程实现
5.3.1 数据表
技巧01:product中的provider_id字段和provider的id字段作为关联查询的桥梁
技巧02:理论上说,product中的provider_id字段应该设置成外键,但是为了开发方便,此处不进行设置【PS: 开发人员知道这是外键即可】
/* Navicat MySQL Data Transfer Source Server : mysql5.4 Source Server Version : 50540 Source Host : localhost:3306 Source Database : testdemo Target Server Type : MYSQL Target Server Version : 50540 File Encoding : 65001 Date: 2018-08-14 20:25:59 */ SET FOREIGN_KEY_CHECKS=0; -- ---------------------------- -- Table structure for `product` -- ---------------------------- DROP TABLE IF EXISTS `product`; CREATE TABLE `product` ( `id` int(11) NOT NULL AUTO_INCREMENT, `product_name` varchar(255) NOT NULL, `product_price` double NOT NULL, `provider_id` int(11) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4; -- ---------------------------- -- Records of product -- ---------------------------- INSERT INTO `product` VALUES ('1', '天友纯牛奶', '3', '1'); INSERT INTO `product` VALUES ('2', '天友核桃花生奶', '2', '1'); INSERT INTO `product` VALUES ('3', '福特福克斯', '13', '2'); INSERT INTO `product` VALUES ('4', '福特嘉年华', '9', '2'); -- ---------------------------- -- Table structure for `provider` -- ---------------------------- DROP TABLE IF EXISTS `provider`; CREATE TABLE `provider` ( `id` int(11) NOT NULL AUTO_INCREMENT, `provider_name` varchar(255) NOT NULL, `provider_phone` varchar(255) NOT NULL, `provider_address` varchar(255) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4; -- ---------------------------- -- Records of provider -- ---------------------------- INSERT INTO `provider` VALUES ('1', '天友乳业', '13282882818', '重庆市大足区'); INSERT INTO `provider` VALUES ('2', '长安制造', '13212112121', '重庆市渝北区');
5.3.2 创建实体类
技巧01:利用IDEA自动生成
技巧02:ProductInfoDTO这个实体类是用来封装产品信息和供应商信息的
package cn.xiangxu.jpa_demo04.domain.domain_do; import lombok.ToString; import javax.persistence.*; /** * @author 王杨帅 * @create 2018-08-14 19:54 * @desc **/ @Entity @ToString @Table(name = "product", schema = "testdemo", catalog = "") public class ProductDO { private int id; private String productName; private double productPrice; private int providerId; @Id @Column(name = "id") public int getId() { return id; } public void setId(int id) { this.id = id; } @Basic @Column(name = "product_name") public String getProductName() { return productName; } public void setProductName(String productName) { this.productName = productName; } @Basic @Column(name = "product_price") public double getProductPrice() { return productPrice; } public void setProductPrice(double productPrice) { this.productPrice = productPrice; } @Basic @Column(name = "provider_id") public int getProviderId() { return providerId; } public void setProviderId(int providerId) { this.providerId = providerId; } @Override public boolean equals(Object object) { if (this == object) return true; if (object == null || getClass() != object.getClass()) return false; ProductDO productDO = (ProductDO) object; if (id != productDO.id) return false; if (Double.compare(productDO.productPrice, productPrice) != 0) return false; if (providerId != productDO.providerId) return false; if (productName != null ? !productName.equals(productDO.productName) : productDO.productName != null) return false; return true; } @Override public int hashCode() { int result; long temp; result = id; result = 31 * result + (productName != null ? productName.hashCode() : 0); temp = Double.doubleToLongBits(productPrice); result = 31 * result + (int) (temp ^ (temp >>> 32)); result = 31 * result + providerId; return result; } }
package cn.xiangxu.jpa_demo04.domain.domain_do; import lombok.ToString; import javax.persistence.*; /** * @author 王杨帅 * @create 2018-08-14 19:54 * @desc **/ @Entity @ToString @Table(name = "provider", schema = "testdemo", catalog = "") public class ProviderDO { private int id; private String providerName; private String providerPhone; private String providerAddress; @Id @Column(name = "id") public int getId() { return id; } public void setId(int id) { this.id = id; } @Basic @Column(name = "provider_name") public String getProviderName() { return providerName; } public void setProviderName(String providerName) { this.providerName = providerName; } @Basic @Column(name = "provider_phone") public String getProviderPhone() { return providerPhone; } public void setProviderPhone(String providerPhone) { this.providerPhone = providerPhone; } @Basic @Column(name = "provider_address") public String getProviderAddress() { return providerAddress; } public void setProviderAddress(String providerAddress) { this.providerAddress = providerAddress; } @Override public boolean equals(Object object) { if (this == object) return true; if (object == null || getClass() != object.getClass()) return false; ProviderDO that = (ProviderDO) object; if (id != that.id) return false; if (providerName != null ? !providerName.equals(that.providerName) : that.providerName != null) return false; if (providerPhone != null ? !providerPhone.equals(that.providerPhone) : that.providerPhone != null) return false; if (providerAddress != null ? !providerAddress.equals(that.providerAddress) : that.providerAddress != null) return false; return true; } @Override public int hashCode() { int result = id; result = 31 * result + (providerName != null ? providerName.hashCode() : 0); result = 31 * result + (providerPhone != null ? providerPhone.hashCode() : 0); result = 31 * result + (providerAddress != null ? providerAddress.hashCode() : 0); return result; } }
package cn.xiangxu.jpa_demo04.domain.domain_do; import lombok.Builder; import lombok.Data; /** * @author 王杨帅 * @create 2018-08-14 20:03 * @desc **/ @Data @Builder public class ProductInfoDTO { private int id; private String productName; private double productPrice; private int providerId; private String providerName; private String providerPhone; private String providerAddress; }
5.3.3 创建持久层接口
技巧01:这里使用@Query进行原生的SQL查询,所以直接在ProductDAO中就可以查询出产品和供应商的信息
package cn.xiangxu.jpa_demo04.repository; import cn.xiangxu.jpa_demo04.domain.domain_do.ProductDO; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import java.util.List; import java.util.Map; /** * @author 王杨帅 * @create 2018-08-14 19:55 * @desc **/ public interface ProductDAO extends JpaRepository<ProductDO, Integer> { @Query( nativeQuery = true, value = "SELECT product_name, product_price, provider_name, provider_phone, provider_address\n" + "FROM product\n" + "JOIN provider \n" + "ON product.provider_id = provider.id\n" + "WHERE product.provider_id = ?1" ) List<Map<String, Object>> findTest(Integer providerId); }
5.3.4 测试
技巧01:多表关联查询时获取到数据是Map类型的,需要进行一层转化;本博文用的是阿里巴巴的 fastjson 进行转化
<dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.1.46</version> </dependency>
坑01:fastjson 转化时 map 的 key 要和 实体类的属性 保持一致,不一致时只有通过本办法实现了
package cn.xiangxu.jpa_demo04.repository; import cn.xiangxu.jpa_demo04.JpaDemo04ApplicationTests; import cn.xiangxu.jpa_demo04.domain.domain_do.DoctorInfoDetail; import cn.xiangxu.jpa_demo04.domain.domain_do.ProductDO; import cn.xiangxu.jpa_demo04.domain.domain_do.ProductInfoDTO; import com.alibaba.fastjson.JSONObject; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.ArrayList; import java.util.List; import java.util.Map; @Component public class ProductDaoTest extends JpaDemo04ApplicationTests { @Autowired private ProductDAO productDAO; @Test public void test01() { List<ProductDO> all = productDAO.findAll(); System.out.println(all); } @Test public void test02() { List<Map<String, Object>> test = productDAO.findTest(2); System.out.println(test); List<ProductInfoDTO> productInfoDTOS = new ArrayList<>(); for (Integer i = 0; i < test.size(); i++) { Map<String, Object> info = test.get(i); // ProductInfoDTO productInfoDTO = JSONObject.parseObject(JSONObject.toJSONString(test.get(i)), ProductInfoDTO.class); ProductInfoDTO productInfoDTO = ProductInfoDTO .builder() .productName((String)info.get("product_name")) .productPrice((Double)info.get("product_price")) .providerName((String)info.get("provider_name")) .providerPhone((String)info.get("provider_phone")) .providerAddress((String)info.get("provider_address")) .build(); System.out.println(productInfoDTO); productInfoDTOS.add(productInfoDTO); } System.out.println(productInfoDTOS); } }