Spring Data Jpa学习笔记

ORM思想及JPA规范

JDBC

JDBC自身理解

我们之前学习的JDBC本身也是一套规范,MySQL和Oracle等数据库公司根据规范开发驱动,我们便实现一套Java代码,便可操作数据库,在不改变Java代码的情况下,修改Driver驱动便可实现操作数据库


在这里插入图片描述


JDBC连接数据库的代码

这里以MySQL为例子,回顾着写一下链接数据库的代码:

  1. Statement方法
/**
* Statement方法
*/
//注册驱动
class.forName(com.mysql.jdbc.com);
//建立链接
Connection con = DriverManager.getConnection(url,user,password);
//创建对象
Statement st = createStatement();
//执行查询返回结果集
String sql = "select * from user_table";
Result rs = st.excuteQuery(sql);

  1. PreparStatement方法
/**
* PreparStatement方法
*/
String sql = "select * from user_table";
//注册驱动
class.forName(com.mysql.jdbc.com);
//建立链接
Connection con = DriverManager.getConnection(url,user,password);
//创建对象
PrepardStatement pst = con.preparStatement(sql);
pst.setString(1,);//从1编号,写每个占位符?的值

//执行查询返回结果集
Result result = pst.executeQuery();

试问Statement 和 preparStatement 的区别

方法区别在于preparStatement的提高性能 预处理 提高安全性
重点说下安全性 在登陆账号密码的时候 password稍加改动便可绕过验证直接登录
例如sql语句为:

select * from user_table where name = '' and password = ''

如果用户密码 password 输入的值为 ==’ or ’ 1 ’ = ’ 1 ==
则sql语句变为:

select * from user_table where name = '随意' and password = '' or ' 1  ' = ' 1 '
//则OR 1 =1  为 true 直接登陆成功

preparStatement方法用占位符的预处理避免了这个问题。


ORM思想:

ORM : 对象关系映射
在面向对象开发的过程中的,通过ORM就可把对象映射到关系型数据库中,使程序能够做到建立对象和数据库的关联,用户操作实体类对象便可以操作数据库

建立两个映射关系

  1. 建立实体类和表的映射关系
  2. 建立实体类中的属性和表中字段的映射关系

目的:

  • 操作实体类就相当于操作数据库表

实现ORM思想的框架有

  • Mybatis
  • Hibernate

JPA规范

JPA规范由SUN公司定义的一套规范,内部由接口和抽象类组成。

实现JPA规范的框架有hibernate 和 toplink 等框架。hibernate除了作为ORM框架之外,也是一种JPA实现。我们以hibernate作为了解JPA规范的框架。


JPA搭建环境

  1. 创建maven工程导入坐标
  2. 配置jpa的核心配置文件
  3. 编写客户的实体类
  4. 配置实体类和表,实体类中的属性和表中的字段的映射关系
  5. 增删改查

1 创建maven工程导入坐标

pom.xml


<properties>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
            <project.hibernate.version>5.0.7.Final</project.hibernate.version>
    </properties>

    <dependencies>
<!--        junit 单元测试-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>

<!--        hibernate 对jpa的支持包-->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-entitymanager</artifactId>
            <version>${project.hibernate.version}</version>
        </dependency>

<!--        c3p0 数据库连接池-->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-c3p0</artifactId>
            <version>${project.hibernate.version}</version>
        </dependency>

<!--        MySQL驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.6</version>
        </dependency>

<!--        log4j 日志-->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>

<!--        lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.16.18</version>
        </dependency>
    </dependencies>


2 配置JPA的核心配置文件

JPA的核心配置文件位于:类路径下一个META-INF文件夹下。命名为:persistence.xml

在这里插入图片描述

persistence.xml

<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="2.0">
<!--    需要配置persistence-unit节点
        持久化单元
            name:持久化单元名称,可以随便起名字
            transaction-type:事务管理的方式
                TA:分布式事务管理
                RESOURCE_LOCAL:本地事务管理
        -->
    <persistence-unit name="myJpa" transaction-type="RESOURCE_LOCAL">
        <!--jpa的快速实现方式-->
        <!--数据库信息-->
        <!--可选配置:配置jpa实现方的配置信息-->


        <!--hibernate作为实现方式-->
        <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>

        <properties>
            <!--数据库信息(用户名&密码&驱动&数据库地址)-->
            <property name="javax.persistence.jdbc.user" value="root"/>
            <property name="javax.persistence.jdbc.password" value="root"/>
            <property name="javax.persistence.jdbc.diver" value="com.mysql.jdbc.Driver"/>
            <property name="javax.persistence.jdbc.url" value="jdbc:mysql:///jpa"/>

            <!--可选配置:配置jpa实现方的配置信息
                显示sql :  true|false
                自动创建数据库表  :  create|update|none-->
            <property name="hibernate.show_sql" value="true"/>
            <property name="hibernate.hbm2ddl.auto" value="update"/>
        </properties>

    </persistence-unit>

</persistence>

注意自动创建数据库表的三个命令区别:hibernate.hbm2ddl.auto

  • create 创建表(如果数据库中有表,则先删除表,再建新表)
  • update 创建表(如果数据库有表,不删除)
  • none 不建表

3 编写客户的实体类

因为我使用了LomBok插件,所以省略了getter、setter、toString方法

Customer.java

/**
 * 客户实体类
 */
@Data
public class Customer {
    
    private Long custId;//主键

    private String custName;//名称
    
    private String custSource;//客户来源

    private String custLevel;//客户级别

    private String custIndustry;//客户所属行业

    private String custPhone;//联系方式

    private String custAddress;//地址

}


4 配置实体类和表,类中属性和表中字段映射关系

配置实体类和表

  • @Entity: 声明实体类
  • @Table: 实体类映射的表 name = “表名”

配置实体类中属性和表中字段

  • @Id: 声明主键
  • @GeneratedValue 配置主键的生成策略
  • Column(name = “字段名”)
/**
 * 客户实体类
 */

/**
 * 第一步:配置实体类与表的映射关系
 * Entity: 声明实体类
 * Table: 实体类映射的表  name = "表名"
 */

@Data
@Entity
@Table(name = "cst_customer")
public class Customer {
    /**
     * 第二步:配置属性与表的字段名
     * Id: 声明主键
     * GeneratedValue 配置主键的生成策略
     * (strategy = GenerationType.IDENTITY) 自增
     * Column(name = "字段名")
     */

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "cust_id")
    private Long custId;//主键

    @Column(name = "cust_name")
    private String custName;//名称

    @Column(name = "cust_source")
    private String custSource;//客户来源

    @Column(name = "cust_level")
    private String custLevel;//客户级别

    @Column(name = "cust_industry")
    private String custIndustry;//客户所属行业

    @Column(name = "cust_phone")
    private String custPhone;//联系方式

    @Column(name = "cust_address")
    private String custAddress;//地址

}

各个注解在代码块中有注释。


5 JPA测试CRUD

操作步骤:

  1. 加载配置文件创建实体管理类工厂对象 Persistence.createEntityManagerFactory();
  2. 通过实体管理器工厂获取实体管理器 factory.createEntityManager();
  3. 创建并开启事务 em.getTransaction();
  4. CRUD
  5. 提交事务(回滚事务)tx.commit();
  6. 释放资源
加载配置文件创建实体管理类工厂对象Persistence.createEntityManagerFactory();
通过实体管理器工厂获取实体管理器factory.createEntityManager();
创建entityManager.getTransaction();
开启事务transaction.begin();
CRUD见下表
提交事务(回滚事务)transaction.commit();
释放资源
persist保存
merge更新
remove删除
find查询(根据id)
getReference查询(根据id)

find 和 getReference 的区别:
find : 立即加载 凡调用到find方法便立即发送查询sql语句
getReference : 延迟加载 执行方法不发送sql语句 什么时候用到,什么时候发送查询语句


public class CustomerTest {

    @Test
    public void testSave(){
        //1.加载配置文件创建工厂(实体管理类工厂)对象
        EntityManagerFactory factory = Persistence.createEntityManagerFactory("myJpa");//myJpa为配置文件设置的持久层名字
        //2.通过实体管理类工厂获取实体管理器
        EntityManager em = factory.createEntityManager();
        //3.获取事务对象
        EntityTransaction tx = em.getTransaction();
        tx.begin();//开启事务
        //4.完成增删改查--保存一个客户
        Customer customer = new Customer();
        customer.setCustName("奥里斯");
        customer.setCustSource("CSDN");
        customer.setCustIndustry("CSDNBLOG");
        customer.setCustLevel("NO.1");
        customer.setCustAddress("人民路");
        customer.setCustPhone("1888888888");

        em.persist(customer);//保存操作
        //5.提交事务
        tx.commit();
        //6.释放资源
        em.close();
        factory.close();
    }
}

JpaUtils类

由于每次执行持久层和数据库打交道的操作,便创建一次实体管理类工厂,这样的缺点是浪费资源和耗时
所以我们可以创建一个JpaUtils.java 利用静态代码块的方法实现只执行一次创建工厂的操作

JpaUtils.java


/**
 * 创建公共的工厂对象
 *
 * 为了解决EntityManagerFactory实体管理器工厂的浪费资源和耗时问题
 */

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

/**
 * 通过静态代码块的形式:当第一次访问此工具类的时候,创建一个公共的实体管理器工厂对象
 * 第二次访问:直接通过一个创建好的公共工厂factory对象,创建实体管理器EntityManager
 */
public class JpaUtils {

    private static EntityManagerFactory factory;

    static {
        factory = Persistence.createEntityManagerFactory("myJpa");
    }

    //工厂生产实体管理器
    public static EntityManager getEntityManager(){
        return factory.createEntityManager();
    }
}


增加

public void testSave(){

        //创建实体管理器
        EntityManager entityManager = JpaUtils.getEntityManager();
        //创建事务
        EntityTransaction transaction = entityManager.getTransaction();
        //开始事务
        transaction.begin();
        //创建客户对象 然后 保存操作
        Customer customer = new Customer();
        customer.setCustName("张峰");
        customer.setCustSource("爱唱歌");
        customer.setCustIndustry("我是歌手");
        customer.setCustLevel("NO.2");
        customer.setCustAddress("朝阳区");
        customer.setCustPhone("1868666666");

        //实体管理器保存实体
        entityManager.persist(customer);
        //提交事务
        transaction.commit();
        //释放资源
        entityManager.close();

    }

更新

public void testUpdate(){

        //创建实体管理器
        EntityManager entityManager = JpaUtils.getEntityManager();
        //创建事务
        EntityTransaction transaction = entityManager.getTransaction();
        //开始事务
        transaction.begin();
        /**
         * merge 方法更新
         * 更新分为三步:
         *      1 --> 先根据id查询,并赋值给实体类对象
         *      2 --> 实体类set方法重新赋值需要更新的值
         *      3 --> merge方法更新
         */
        //1.查询
        Customer customer = entityManager.find(Customer.class, 1l);
        //2.赋值
        customer.setCustName("三哥");

        //3.merge方法更新
        entityManager.merge(customer);
        //提交事务
        transaction.commit();
        //释放资源
        entityManager.close();

    }

删除

public void testDelete(){

        //创建实体管理器
        EntityManager entityManager = JpaUtils.getEntityManager();
        //创建事务
        EntityTransaction transaction = entityManager.getTransaction();
        //开始事务
        transaction.begin();
        /**
         * remove 方法删除
         * 删除分为两步:
         *      1 --> 先根据id查询想要删除的数据 赋值给一个对象
         *      2 --> 删除对象
         */
        Customer customer = entityManager.find(Customer.class, 2l);

        //remove删除
        entityManager.remove(customer);
        //提交事务
        transaction.commit();
        //释放资源
        entityManager.close();

    }

查找

public void testFind(){

        //创建实体管理器
        EntityManager entityManager = JpaUtils.getEntityManager();
        //创建事务
        EntityTransaction transaction = entityManager.getTransaction();
        //开始事务
        transaction.begin();
        //创建客户对象 然后 查询
        /**
         * 查询find和getReference --> 根据id查询
         *      class: 查询数据的结果需要包装的实体类类型字节码
         *              比如查询为顾客信息customer,结果类型封装为customer
         *      id: 主键值
         *
         *  方法区别:
         *  find: 立即加载  凡调用到find方法便立即发送查询sql语句
         *  getReference: 延迟加载  执行方法不发送sql语句  什么时候用到,什么时候发送查询语句
         *
         */
        Customer customer = entityManager.find(Customer.class, 1l);//id为long长整型  需要专为1L
        System.out.println(customer);
        //提交事务
        transaction.commit();
        //释放资源
        entityManager.close();

    }

JPQL语句

jpql语句是操作实体类的
jpql语句与mysql语句相似
jpql 不支持select * ,但支持select 其他语句照搬然后微调
微调:表名修改为实体类名,字段名修改为实体类属性名

Jpql 语句是操作实体类的:
分为三步:

  1. 创建jpql对象
  2. 对参数赋值
  3. 查询返回封装结果集

必须创建query对象,query对象才是执行jpql的对象

查询全部(顺序)
//查询---顺序
public void testSelect(){
        //1. 创建实体管理器
        EntityManager em = JpaUtils.getEntityManager();
        //2. 创建事务 并开启
        EntityTransaction ts = em.getTransaction();
        ts.begin();

        /**
         * jpql由于不支持select *   , 表名对应映射的实体类名
         * mysql : select * from cst_customer;
         * jpql : from Customer
         */
        //3. 创建Query对象, query对象才是执行jpql的对象
        String jpql = "from Customer";
        Query query = em.createQuery(jpql);
        //query.getResultList()方法查询结果并封装结果集为list
        List list = query.getResultList();
        //打印
        for (Object obj : list
             ) {
            System.out.println(obj);//由于结果集中包含实体类属性中各个类型 所以用Object上帝类
        }

        //4. 提交事务
        ts.commit();

        //5. 释放资源
        em.close();


    }

查询全部(倒序)
//查询---倒序
public void testSelectDesc(){
        //1. 创建实体管理器
        EntityManager em = JpaUtils.getEntityManager();
        //2. 创建事务 并开启
        EntityTransaction ts = em.getTransaction();
        ts.begin();

        /**
         * mysql : select * from cst_customer order by cust_id desc ;
         * jpql : from Customer order by custId desc
         */

        //3. 创建Query对象, query对象才是执行jpql的对象
        String jpql = "from Customer order by custId desc";
        Query query = em.createQuery(jpql);

        List list = query.getResultList();

        for (Object obj : list
        ) {
            System.out.println(obj);
        }

        //4. 提交事务
        ts.commit();

        //5. 释放资源
        em.close();

    }

聚合函数–统计count
/**
 * JPQL与MySQL语句相似,
 * mysql中表名对应jpql中的实体类名,mysql中的字段名对应jpql中的实体类属性
 * jpql不支持select *    但是支持select
 */
 //统计查询
public void testCount(){
        /**
         * mysql : select count(cust_id) from cst_customer;
         * jpql : select count(custId) from Customer;
         */
        EntityManager entityManager = JpaUtils.getEntityManager();
        EntityTransaction transaction = entityManager.getTransaction();
        transaction.begin();

        //创建query对象
        String jpql = "select count(custId) from Customer";
        Query query = entityManager.createQuery(jpql);

        //因为count计数只有一个  并不是结果集 所以不用getResultList()
        //query.getSingleResult() 结果为单一的方法
        Object singleResult = query.getSingleResult();
        System.out.println(singleResult);

        //提交事务
        transaction.commit();

        //释放资源
        entityManager.close();
    }

分页
//分页查询
public void testPage(){
        /**
         * mysql : select * from cst_customer limit 0,2;
         *          select * from cst_customer limit ?,?;
         * jpql : from cst_customer limit ?,?
         *        ? 设置分页参数 jpql中的第二步
         */
        //工厂创建实体管理器
        EntityManager entityManager = JpaUtils.getEntityManager();
        //实体管理器创建事务
        EntityTransaction transaction = entityManager.getTransaction();
        transaction.begin();//开启事务

        //1. 创建query对象
        String jpql ="from Customer";
        Query query = entityManager.createQuery(jpql);

        //2. 设置参数
        query.setFirstResult(0);//设置起始索引
        query.setMaxResults(2);//设置单次查询条数

        //3. 封装结果集
        List resultList = query.getResultList();

        //for each 方法遍历打印
//        for (Object obj : resultList
//             ) {
//            System.out.println(obj);
//        }

        //迭代器方法遍历打印
        Iterator<Object> it = resultList.iterator();
        while(it.hasNext()){
            System.out.println(it.next());
        }

        //提交事务
        transaction.commit();
        //释放资源
        entityManager.close();

    }

条件查询–模糊查询
//模糊查询
public void testLike(){
        /**
         * mysql : select * from cst_customer where cust_name like '张%';
         * jpql : from cst_customer where cust_name like ?
         */
        //工厂创建实体管理器
        EntityManager entityManager = JpaUtils.getEntityManager();
        //实体管理器创建事务
        EntityTransaction transaction = entityManager.getTransaction();
        transaction.begin();//开启事务

        //1. 创建query对象
        String jpql ="from Customer where custName like ?";
        Query query = entityManager.createQuery(jpql);

        //2. 设置参数 query.setParameter(第几个占位符,"value值");
        query.setParameter(1,"张%");

        //3. 封装结果集
        List resultList = query.getResultList();

        //迭代器方法遍历打印
        Iterator<Object> it = resultList.iterator();
        while(it.hasNext()){
            System.out.println(it.next());
        }

        //提交事务
        transaction.commit();
        //释放资源
        entityManager.close();

    }

封装结果集query.getResultList();
单个数据结果query.getSingleResult();
设置占位符和参数query.setParameter(第几个占位符,“value值”);
起始索引query.setFirstResult(0);
最大索引(每页条数)query.setMaxResults(15);

Spring Data Jpa

Spring Data Jpa概述

Spring Data Jpa是Spring基于ORM框架,JPA规范的基础上封装的一套JPA应用框架。
Spring Data Jpa使得我们解脱了DAO层的操作,基本上所有的CRUD都可以依赖它实现。
实际的工作用推荐Spring Data Jpa + ORM完成。这样优点是切换不同的ORM方便,使数据层更加简单,方便解耦。

简化数据访问层代码,使用了Spring Data Jpa,我们致谢dao层接口,便自动具备了基本的CRUD和分页查询等操作
在这里插入图片描述


Spring Data Jpa入门操作

基于上面的客户表案例

搭建SpringDataJpa环境

  1. 创建maven工程导入坐标
  2. 配置Spring的配置文件
创建maven

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>cn.itcast</groupId>
    <artifactId>jpa-day2</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <spring.version>5.0.2.RELEASE</spring.version>
        <hibernate.version>5.0.7.Final</hibernate.version>
        <slf4j.version>1.6.6</slf4j.version>
        <log4j.version>1.2.12</log4j.version>
        <c3p0.version>0.9.1.2</c3p0.version>
        <mysql.version>5.1.6</mysql.version>
    </properties>

    <dependencies>
        <!-- junit单元测试 -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>

        <!-- spring beg -->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.6.8</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <!-- spring对orm框架的支持包-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-orm</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <!-- spring end -->

        <!-- hibernate beg -->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-core</artifactId>
            <version>${hibernate.version}</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-entitymanager</artifactId>
            <version>${hibernate.version}</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-validator</artifactId>
            <version>5.2.1.Final</version>
        </dependency>
        <!-- hibernate end -->

        <!-- c3p0 beg -->
        <dependency>
            <groupId>c3p0</groupId>
            <artifactId>c3p0</artifactId>
            <version>${c3p0.version}</version>
        </dependency>
        <!-- c3p0 end -->

        <!-- log end -->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>${log4j.version}</version>
        </dependency>

        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>${slf4j.version}</version>
        </dependency>

        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>${slf4j.version}</version>
        </dependency>
        <!-- log end -->


        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${mysql.version}</version>
        </dependency>

        <!-- spring data jpa 的坐标-->
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-jpa</artifactId>
            <version>1.9.0.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <!-- el beg 使用spring data jpa 必须引入 -->
        <dependency>
            <groupId>javax.el</groupId>
            <artifactId>javax.el-api</artifactId>
            <version>2.2.4</version>
        </dependency>

        <dependency>
            <groupId>org.glassfish.web</groupId>
            <artifactId>javax.el</artifactId>
            <version>2.2.4</version>
        </dependency>
        <!-- el end -->

        <!--        lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.16.18</version>
        </dependency>
    </dependencies>

</project>


配置文件

配置Spring的配置文件,在类路径resource下,创建一个applicationContext.xml文件。配置spring data jpa与spring的整合

配置文件主要包括

  • 创建连接池。
  • 创建entityManagerFactory工厂对象交给spring容器管理
  • 配置事务管理器
  • 声明式事务一般用在service层,本案例不涉及
  • 整合spring data Jpa
  • 配置包扫描

下面配置文件有三个包扫描,意思分别是:

扫描实体类所在包name=“packagesToScan” value=“com.iyiliang.domain”
扫描dao所在的包base-package=“com.iyiliang.dao”
扫描所有spring注解的包base-package=“com.iyiliang”

applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:jpa="http://www.springframework.org/schema/data/jpa" xmlns:task="http://www.springframework.org/schema/task"
       xsi:schemaLocation="
		http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
		http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
		http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
		http://www.springframework.org/schema/data/jpa
		http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">

    <!--spring 和 spring data jpa的配置-->

    <!-- 1.创建entityManagerFactory对象交给spring容器管理-->
    <bean id="entityManagerFactoty" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <!--配置的扫描的包(实体类所在的包)Spring容器一旦创建,便会在该路径下寻找带有@Entity @Table的实体类 -->
        <property name="packagesToScan" value="com.iyiliang.domain" />
        <!-- jpa的实现厂家 -->
        <property name="persistenceProvider">
            <bean class="org.hibernate.jpa.HibernatePersistenceProvider"/>
        </property>

        <!--jpa的供应商适配器 -->
        <property name="jpaVendorAdapter">
            <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
                <!--配置是否自动创建数据库表 -->
                <property name="generateDdl" value="false" />
                <!--指定数据库类型 -->
                <property name="database" value="MYSQL" />
                <!--数据库方言:支持的特有语法 -->
                <property name="databasePlatform" value="org.hibernate.dialect.MySQLDialect" />
                <!--是否显示sql -->
                <property name="showSql" value="true" />
            </bean>
        </property>

        <!--jpa的方言 :高级的特性 -->
        <property name="jpaDialect" >
            <bean class="org.springframework.orm.jpa.vendor.HibernateJpaDialect" />
        </property>

    </bean>

    <!--2.创建数据库连接池 因为用的c3p0,所以用c3p0.ComboPooledDataSource类-->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="user" value="root"></property>
        <property name="password" value="root"></property>
        <property name="jdbcUrl" value="jdbc:mysql:///jpa" ></property>
        <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
    </bean>

    <!--3.整合spring data Jpa-->
    <!-- 配置dao所在的包,配置事务和工厂,因为spring data jpa封装的jpa,所以还要配置这些。-->
    <jpa:repositories base-package="com.iyiliang.dao" transaction-manager-ref="transactionManager"
                      entity-manager-factory-ref="entityManagerFactoty" ></jpa:repositories>

    <!--4.配置事务管理器 -->
    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="entityManagerFactoty"></property>
    </bean>

    <!-- 4.txAdvice-->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="save*" propagation="REQUIRED"/>
            <tx:method name="insert*" propagation="REQUIRED"/>
            <tx:method name="update*" propagation="REQUIRED"/>
            <tx:method name="delete*" propagation="REQUIRED"/>
            <tx:method name="get*" read-only="true"/>
            <tx:method name="find*" read-only="true"/>
            <tx:method name="*" propagation="REQUIRED"/>
        </tx:attributes>
    </tx:advice>

    <!-- 5.aop-->
    <aop:config>
        <aop:pointcut id="pointcut" expression="execution(* cn.itcast.service.*.*(..))" />
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut" />
    </aop:config>


    <!--5.声明式事务,一般用在service层,本案例不涉及 -->

    <!-- 6. 配置包扫描 带有spring注解的包-->
    <context:component-scan base-package="com.iyiliang" ></context:component-scan>
</beans>


编写实体类并配置映射关系

Customer.java

/**
 * 客户实体类
 */
@Data
@Entity
@Table(name = "cst_customer")
public class Customer {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "cust_id")
    private Long custId;//主键

    @Column(name = "cust_name")
    private String custName;//名称

    @Column(name = "cust_source")
    private String custSource;//客户来源

    @Column(name = "cust_level")
    private String custLevel;//客户级别

    @Column(name = "cust_industry")
    private String custIndustry;//客户所属行业

    @Column(name = "cust_phone")
    private String custPhone;//联系方式

    @Column(name = "cust_address")
    private String custAddress;//地址

}


编写dao层接口

编写一个符合Spring Data Jpa 的dao层接口

只需要写dao层接口,不需要编写dao层接口的实现类

dao层接口规范:

  1. dao层接口需要继承两个接口(JpaRepository<T, ID>,JpaSpecificationExecutor< T >
  2. 需要提供相应的泛型
基础CRUD接口JpaRepository<实体类类型, 主键类型>
分页等复杂功能接口JpaSpecificationExecutor<实体类类型>
/**
 * 客户实体类接口
 *
 */
public interface CustomerDao extends JpaRepository<Customer,Long>, JpaSpecificationExecutor<Customer> {

}

测试dao接口–spring data jpa内部方法实现CRUD

只写了这个dao接口 不写实现类,现在已经具备CRUD功能。下面简单的看几个吧。

用Junit4 首先建立test 测试 。然后因为交给了Spring容器完成所以指明@RunWith和@ContextConfiguration配置文件地址

/**
 * 因为交给了Spring容器完成所以指明@RunWith和配置文件地址
 */

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class CustomerDaoTest {

    @Autowired
    private CustomerDao customerDao;
    
 }

查询

查询通过ID。 如果需要通过其他属性查询,需自己在接口写方法,下面具体讲解。

//查询一个
    @Test
    public void testFindOne() {
        Customer customer = customerDao.findOne(3l);//ID为long型
        System.out.println(customer);
    }
    

查询所有 结果为List集合

//查询所有(List集合)
 @Test
 public void testFindAll() {
     List<Customer> all = customerDao.findAll();
     for (Object obj: all
          ) {
         System.out.println(obj);
     }

 }
保存

保存


//测试保存方法 save()方法可用于新增和更新
    @Test
    public void testSave(){
        //new一个客户
        Customer customer = new Customer();
        customer.setCustName("阿尔托");
        customer.setCustPhone("011544886");
        customer.setCustAddress("Alto City");
        customer.setCustIndustry("Adventure冒险");
        customer.setCustLevel("top1");
        customer.setCustSource("推荐");

        //保存这个客户信息
        customerDao.save(customer);

    }
    
修改

修改信息 先查询后修改

/**
     * save方法用于保存和更新
     * 当数据库中没有该id的时候,执行保存操作
     * 当数据库中有该id时候,用find先查到赋值为customer,再用set方法设置修改的值
     */
    @Test
    public void testUpadate(){
        //查询一个客服赋值为customer 
        Customer customer = customerDao.findOne(6l);//id为long型
        customer.setCustPhone("011544886");
        customer.setCustAddress("昌平区");
        customer.setCustSource("推荐");

        //保存这个客户信息
        customerDao.save(customer);

    }
删除

删除

//删除操作
    public void testDelete(){
        customerDao.delete(3l);//id为long型
    }

SpringDataJpa运行原理分析

  1. 通过JdkDynamicAopProxy的invoke方法创建了一个动态代理对象 SimpleJpaRepository
  2. SimpleJpaRepository当中封装了JPA的操作(借助JPA的api完成数据库的CRUD)
  3. 通过hibernate完成数据库操作(封装了jdbc)

复杂查询(jpql,sql,方法名)

jpql查询方式和sql类似。除了上面讲到的sql操作数据库表,jpql操作实体类对象。还有一点需要修改
@Query里面有个nativeQuery属性

nativeQuerytruefalse(默认)
本地查询sql查询jpql查询(默认)

如果返回客户信息是多个,需要list集合


Jpql查询方式

对于一些内部没有定义的方法,比如上面只能通过ID查询。如果通过名字查询则自己定义。

1…写在dao接口
2…在新添加的方法上注解@Query(value = “jpql语句”)

下面的几个例子上面分别为dao接口里新增的方法!
下面的为Junit4单元测试用例!

例子1:

	//根据姓名查询 from Customer where custName = ?
    //因为需要返回一个客户信息 所以是customer类型
    @Query(value = "from Customer where custName = ?")
    public Customer findByName(String name);

   
    //JPQL根据姓名查询
    @Test
    public void testFindByName(){
        System.out.println(customerDao.findByName("卢哈哈"));

    }

例子2:

/**
     * 当有多个占位符时
     * 一般参数顺序默认和占位符顺序保持一致
     * 若顺序不想保持一致可以加索引(问号+数字)
     */
    //根据姓名 手机号查询 from Customer where custName = ? and custPhone = ?
    @Query(value = "from Customer where custName = ?2 and custPhone = ?1 ")
    public Customer findByNameAndPhone(String phone, String name);


//根据姓名 手机号查询
    @Test
    public void testByNameAndPhone(){
        Customer customer = customerDao.findByNameAndPhone("1888666666", "卢哈哈");
        System.out.println(customer);
    }

例子3:

/**
     * @Query 为查询操作
     * 要进行更新 需要加注解@Modifying
     */
    //根据id修改姓名 update Customer set custName = ? where id = ?
    //修改信息不用返回 所以void类型
    @Modifying
    @Query(value = "update Customer set custName = ?1 where custId = ?2")
    public void updateNameById(String name,Long id);


	//根据id修改姓名
    @Test
    @Transactional //添加事务支持
    @Rollback(value = false) //默认回滚,所以设置回滚为"否"
    public void testUpdateNameByName(){
        customerDao.updateNameById("奥德赛",5l);
    }

需要 注意 的是:
@Query执行的是查询
如果涉及到更新操作 ,需要加注解@Modifying


sql查询方式

需要注意的是sql特有的查询返回的是list的数组,所以可用Object [] ,理解为一维数组

下面的几个例子上面分别为dao接口里新增的方法!
下面的为Junit4单元测试用例!

例子1:

	//sql模糊查询188开头手机号
    @Query(value = "select * from cst_customer where cust_phone like ? ",nativeQuery = true)
    public List<Object[]> findByPhone(String phone);



	//sql模糊查询188开头手机号
    @Test
    public void testSqlByPhone(){
        List<Object[]> customers = customerDao.findByPhone("188%");
        for (Object[] obj : customers
             ) {
            System.out.println(Arrays.toString(obj));
        }

    }

例子2:

	//查询所有
    @Query(value = "select * from cst_customer " , nativeQuery = true)
    public List<Object []> findAllBySql();


	//sql查询所有
    @Test
    public void testFindAllBySql(){
        List<Object[]> cou = customerDao.findAllBySql();
        for (Object[] c: cou
             ) {
            System.out.println(Arrays.toString(c));
        }
    }

方法名查询

前方高能!!!
方法名规则不写jpql语句只写符合spring data jpa 方法名称规范的方法名,便自动可实现查询

写法为:findBy 开头代表查询 + 属性名称(首字母大写)+查询方式(默认为=,还有其他Like,IsNull,Not等)+ And(Or)…写法和sql相同

看下面两个例子

	//findBy + 属性名称 (按照属性名称完成匹配)
    public Customer findByCustName(String name);


    //findBy + 属性名称 + “查询方式(Like | IsNull 等等)”
    //多条件查询加 And | Or 等
    public List<Customer> findByCustLevelAndCustPhoneLike(String level, String phone);

Specifications动态查询

上面学习的是基于接口JpaRepository<T,ID>实现的CURD,现在学习第二个接口JpaSpecificationExecutor< T >
specification 规范 executor 执行人

JpaSpecificationExecutor 方法列表

  1. T findOne(Specification spec); //查询单个对象
  2. List< T > findAll(Specification spec); //查询列表
  3. Page< T > findAll(Specification spec, Pageable pageable);
  4. List< T > findAll(Specification spec, Sort sort);
  5. long count(Specification spec);//统计查询

查询步骤

Specification :查询条件

  • 自定义我们的Specification实现类,
    • 即创建匿名内部类实现topredicatef方法
  • 借助参数完成查询
    • root : 获取需要的对象属性
    • CriteriaQuery:顶层查询对象,自定义查询方式
    • CriteriaBuilder : 构造的条件(like模糊查询,equal等于查询。。等)
例子1

例子1:借助名字查询


    //Spec按姓名查询
    @Test
    public void testSpecSelectByName(){

        //匿名内部类
        Specification<Customer> sp = new Specification<Customer>() {
            public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
                //root.get(按需求获取实体类对象属性)
                Path<Customer> custName = root.get("custName");
                //cb.equal(path对象,比较的值) 精准查询
                Predicate cb = criteriaBuilder.equal(custName,"阿尔托");

                //返回结果封装的cb
                return cb;
            }
        };
        //调用返回的结果
        Customer one = customerDao.findOne(sp);
        System.out.println(one);
    }


例子2

例子2:
实现了多条件、模糊、排序三个函数
多条件和模糊组合查并按着ID排序

//多条件和模糊查询---行业精准NO.1和手机号模糊1888%
    @Test
    public void testMoreSpecAndLike(){
        //创建Specification方法
        Specification<Customer> spec = new Specification<Customer>() {

            public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
                //第一个条件属性
                Path<Customer> custLevel = root.get("custLevel");
                //第二个条件属性
                Path<Customer> custPhone = root.get("custPhone");
                //取值
                Predicate eq1 = criteriaBuilder.equal(custLevel, "NO.1");
                /**
                 * like,gt,lt,等模糊查询方法与equal方法不同
                 * equal直接得到path对象(属性)进行比较;
                 * 模糊查询需要得到path,根据path指定参数类型比较
                 * 形式:cb.like(path.as(类型的字节码对象))
                 */
                Predicate eq2 = criteriaBuilder.like(custPhone.as(String.class),"188%");
//                return eq2;
                //合并条件eq1 eq2
                Predicate and = criteriaBuilder.and(eq1,eq2);

                return and;

            }

        };
        /**
         * 排序
         * 实例化sort对象  并传入两个参数
         *  第一个: 正序 Sort.Direction.ASC | 倒序 Sort.Direction.DESC
         *  第二个 : 排序的属性名称
         */
        Sort sort = new Sort(Sort.Direction.ASC,"custId");

        List<Customer> all = customerDao.findAll(spec,sort);
        for (Object obj : all
             ) {

            System.out.println(obj);
        }

    }

例子3

例子3,分页

 /**
     * 分页
     * 创建Pageable对象
     *
     * PageRequest(起始, 每页条数);
     */
    @Test
    public void tesPage(){
        Specification<Customer> spec = null ;
        Pageable pageable = new PageRequest(0, 3);

        Page<Customer> pg = customerDao.findAll(spec, pageable);
        System.out.println(pg.getContent());//得到数据集合列表
//        System.out.println(pg.getSize()); //每页长度
        System.out.println(pg.getTotalElements());//得到总条数
        System.out.println(pg.getTotalPages());//得到总页数


    }

ps:个人觉得没有方法名和自定义方法用jpql语句简单。。。


完成多表操作

分析步骤:

  • 明确表关系(一对一 | 一对多 | 多对多)
  • 确定表关系(确定那个为主表(一)那个为从表(多),或者都为 多,然后明确外键或者建立中间表)
  • 编写实体类,在实体类中描述表关系
    • 一的实体类创建一个多的实体类的集合
    • 多的实体类创建一个一的实体类对象
  • 配置映射关系

一对多

假设客户为一个公司为和 联系人为员工 形成 一对多。

步骤

  1. 明确表关系(一对多)
  2. 确定表关系。主表为客户公司。从表为联系人员工
  3. 编写实体类,在实体类中描述表的包含关系。多的实体类 包含 一个 的对象。。。。。 一个的 包含多的实体类的 集合
  4. 配置映射关系。jpa注解配置一对多的映射关系
//配置客户和联系人之间的关系(一对多关系)
    /**
     * 使用注解的形式配置多表关系
     *      1.声明关系
     *          @OneToMany : 配置一对多关系
     *              targetEntity :对方对象的字节码对象
     *      2.配置外键(中间表)
     *              @JoinColumn : 配置外键
     *                  name:外键字段名称
     *                  referencedColumnName:参照的主表的主键字段名称
     *
     *  * 在客户实体类上(一的一方)添加了外键了配置,所以对于客户而言,也具备了维护外键的作用
     *
     */
     @OneToMany(targetEntity = LinkMan.class)
     @JoinColumn(name = "lkm_cust_id",referencedColumnName = "cust_id")
     private Set<LinkMan> linkMans = new HashSet<LinkMan>();


	//多
	/**
     * 配置联系人到客户的多对一关系
     *     使用注解的形式配置多对一关系
     *      1.配置表关系
     *          @ManyToOne : 配置多对一关系
     *              targetEntity:对方的实体类字节码
     *      2.配置外键(中间表)
     *
     * * 配置外键的过程,配置到了多的一方,就会在多的一方维护外键
     *
     */
    @ManyToOne(targetEntity = Customer.class,fetch = FetchType.LAZY)
    @JoinColumn(name = "lkm_cust_id",referencedColumnName = "cust_id")
    private Customer customer;

由于双方都配置了联系,建立了双向链接。会发送两条insert语句,一条多余的update语句,那我们的解决是思路很简单,就是一的一方放弃维护权

一般一的一方放弃。直接在注解上修改

	/**
	 *放弃外键维护权的配置将如下配置改为
	 */
    //@OneToMany(targetEntity=LinkMan.class)
	//@JoinColumn(name="lkm_cust_id",referencedColumnName="cust_id")	
	//设置为
	@OneToMany(mappedBy="customer")

多对多

假设
两张表分别为 用户表 和 角色表

步骤

  1. 明确表关系 (多对多)
  2. 确定表关系(建立中间表)
  3. 编写实体类,并描述表的包含关系:分别建立对方对象的集合。
  4. 配置映射关系
/**
     * 配置用户到角色的多对多关系
     *      配置多对多的映射关系
     *          1.声明表关系的配置
     *              @ManyToMany(targetEntity = Role.class)  //多对多
     *                  targetEntity:代表对方的实体类字节码
     *          2.配置中间表(包含两个外键)
     *                @JoinTable
     *                  name : 中间表的名称
     *                  joinColumns:配置当前对象在中间表的外键
     *                      @JoinColumn的数组
     *                          name:外键名
     *                          referencedColumnName:参照的主表的主键名
     *                  inverseJoinColumns:配置对方对象在中间表的外键
     */
    @ManyToMany(targetEntity = Role.class,cascade = CascadeType.ALL)
    @JoinTable(name = "sys_user_role",
            //joinColumns,当前对象在中间表中的外键
            joinColumns = {@JoinColumn(name = "sys_user_id",referencedColumnName = "user_id")},
            //inverseJoinColumns,对方对象在中间表的外键
            inverseJoinColumns = {@JoinColumn(name = "sys_role_id",referencedColumnName = "role_id")}
    )
    private Set<Role> roles = new HashSet<>();

级联操作

  • 从表有数据,默认会把外键字段置为null,然后删除主表数据。如果在数据库的表结构上,外键字段有非空约束,默认情况就会报错了。
  • 如果配置了放弃维护关联关系的权利,则不能删除(与外键字段是否允许为null, 没有关系)因为在删除时,它根本不会去更新从表的外键字段了。
  • 如果还想删,使用级联删除
  • 没有表数据引用可删除

级联操作:指操作一个对象同时操作它的关联对象

使用方法:
在注解上配置cascade = “persist | remove | merge | all ”
这几个词和jpa操作数据库意思一样

@OneToMany(mappedBy="customer",cascade=CascadeType.ALL)

对象导航查询

通过已加载的对象,查询他的关联对象

查询一个客户,获取该客户下的所有联系人

	@Autowired
	private CustomerDao customerDao;
	
	@Test
	//由于是在java代码中测试,为了解决no session问题,将操作配置到同一个事务中
	@Transactional 
	public void testFind() {
		Customer customer = customerDao.findOne(5l);
		Set<LinkMan> linkMans = customer.getLinkMans();//对象导航查询
		for(LinkMan linkMan : linkMans) {
  			System.out.println(linkMan);
		}
	}

延迟加载与立即加载

	/**
	 * 在联系人对象的@ManyToOne注解中添加fetch属性
	 * 		FetchType.EAGER	:立即加载
	 * 		FetchType.LAZY	:延迟加载
	 */
	@ManyToOne(targetEntity=Customer.class,fetch=FetchType.EAGER)
	@JoinColumn(name="cst_lkm_id",referencedColumnName="cust_id")
	private Customer customer;

默认情况下,一关联多采用延迟加载。多连一采用立即加载

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值