其实很多框架都是对另一个框架的封装,我们在学习类似的框架的时候,难免会进入误区
Java 持久层框架访问数据库的方式大致分为两种。
以 SQL 核心
,封装一定程度的 JDBC 操作,比如: MyBatis。以 Java 实体类为核心
,将实体类的和数据库表之间建立映射关系,也就是我们说的ORM框架,如:Hibernate、Spring Data JPAJPA全称为Java Persistence API(Java持久层API),是一个基于ORM(或叫O/R mapping ,对象关系映射) 的标准规范,在这个规范中,JPA只定义标准规则,不提供实现。
Java5
中提出的Java持久化规范,内部由一系列的接口和抽象类构成。
可以通过注解或者XML描述对象-关系表
之间的映射关系,并将运行期的实体对象持久化到数据库。需要注意的是JPA
统一了Java应用程序访问ORM框架的规范
,使得应用程序以统一的方式访问持久层
我们知道不同的数据库厂商都有自己的实现类,后来统一规范也就有了数据库驱动
,Java在操作数据库的时候,底层使用的其实是JDBC,而JDBC是一组操作不同数据库的规范。我们的Java应用程序,只需要调用JDBC提供的API
就可以访问数据库了,而JPA也是类似的道理。
JPA为我们提供了以下规范:
ORM映射元数据:JPA支持XML
和注解
两种元数据的形式描述对象和表之间的映射关系
,框架据此将实体对象持久化到数据库表中
如:@Entity、@Table、@Column、@Transient等注解。
JPA 的API:用来操作实体对象,执行CRUD操作,框架在后台替我们完成所有的事情,开发者从繁琐的JDBC和SQL代码中解脱出来
如:entityManager.merge(T t);
JPQL查询语言:通过面向对象而非面向数据库的查询语言查询数据
,避免程序的SQL语句紧密耦合。
JPA定义了独特的JPQL(Java Persistence Query Language),JPQL是EJBQL的一种扩展,它是针对实体的一种查询语言,操作对象是实体,而不是关系数据库的表,而且能够支持批量更新和修改、JOIN、GROUP BY、HAVING 等通常只有 SQL 才能够提供的高级查询特性,甚至还能够支持子查询。
.
如:from Student s where s.name = ?
但是:
JPA仅仅是一种规范,也就是说JPA仅仅定义了一些接口,而接口是需要实现才能工作的。所以底层需要某种实现,而Hibernate就是实现了JPA接口的ORM框架。
JPA是一套ORM规范,Hibernate实现了JPA规范!
Hibernate是一个开源的全自动ORM框架
,它对JDBC进行了非常轻量级的对象封装
,它将实体对象与数据库表建立映射关系
,hibernate可以自动生成SQL语句,自动执行,使得Java程序员可以随心所欲的使用对象编程思维来操纵数据库。
JPA和Hibernate的关系就像
JDBC和JDBC驱动的关系
,JPA是规范,Hibernate除了作为ORM框架之外,它也是一种JPA实现。JPA怎么取代Hibernate呢?JDBC规范可以驱动底层数据库吗?答案是否定的,也就是说,如果使用JPA规范进行数据库操作,底层需要hibernate作为其实现类完成数据持久化工作。
JPA和Hibernate的关系:
规范
,而不是框架JPA的一种实现
,是一个框架Hibernate是一个自动化更强、更高级的框架,在java代码层面上,省去了绝大部分sql编写,取而代之的是用面向对象的方式操作关系型数据库的数据。
而MyBatis则是一个能够灵活编写sql语句,并将sql的入参和查询结果映射成POJO的一个持久层框架。从表面上看,Hibernate很方便、自动化更强,而MyBatis 在Sql语句编写方面则更灵活自由。
Hibernate是面向对象的,而MyBatis是面向关系的。当然,用Hibernate也可以写出面向关系代码和系统,但却得不到面向关系的各种好处,最大的便是编写sql的灵活性,同时也失去面向对象意义和好处——一句话,不伦不类。
一、应用场合:
传统公司、部分个人开发者喜欢用jpa;而互联网公司更青睐于mybatis
二.原因:
- mybatis更加灵活,开发迭代模式决定了他是互联网公司的首先;每一次的修改不会带来性能上的下降。
- 传统公司需求迭代速度慢、项目改动小,hibernate可以做到一劳永逸;hibernate容易因为添加关联关系或者开发者不了解优化导致项目,造成越改越糟糕。
二、各自特点:
- mybatis官方文档就说了他是一个半自动化的持久层框架,相对于按自动的hibernate更加灵活可控;
- mybatis的学习成本低于hibernate。
- 使用hibernate需要对他有深入的了解,尤其是缓存方面,作为一个持久层框架,性能还是第一位的。hibernate具有三级缓存,一级缓存默认是开启的,二级缓存需要手动开始并配置优化,三级缓存可以整合业界流行的缓存技术:redis,ecache等等。
- hibernate在关联查询中的懒加载。(在开发中,还是不建议去过多使用外键去关联操作)
- jpa是一种规范,hibernate也是遵从这种规范;
- springDataJpa是对repository的封装,简化了repository的操作。
灵活性方面,jpa更灵活,包括基本的增删改查、数据关系以及数据库的切换上都比mybatis灵活,但是jpa门槛较高,另外就是
更新数据需要先将数据查出来才能进行更新,数据量大的时候,jpa效率会低一些,这时候需要做一些额外的工作去处理!
Hibernate、Mybatis关系
Spring Data是Spring 社区的一个子项目,主要用于简化数据(关系型&非关系型)访问,其主要目标是使得数据库的访问变得方便快捷。
它提供很多模板操作
Spring Data JPA是Spring Data的子模块,是在实现了JPA规范
的基础上封装
的一套 JPA 应用框架
在使用不同的ORM框架之间进行切换而不需要更改代码。
通过统一ORM框架的访问持久层的操作
,来提高开发的效率。Spring Data JPA也可以理解为 JPA 规范的再次封装抽象
,底层还是使用了 Hibernate 的 JPA 技术实现
Repository 接口:
Repository 实现类:
以上这些类和接口就是我们以后在使用Spring Data JPA的时候需要掌握的
Hibernate是JPA的一种实现,是一个框架
Spring Data JPA不是一个实现或JPA提供的程序
,它是spring提供的一套简化JPA开发的框架,它只是一个抽象层,主要用于减少为各种持久层存储实现数据访问层所需的样板代码量。但是它还是需要JPA提供实现程序,所以Spring Data JPA底层默认使用的就是 Hibernate实现
。
hibernate为JPA的一种实现方式
总结:
CREATE TABLE `cst_customer` (
`cust_id` bigint(20) NOT NULL AUTO_INCREMENT,
`cust_name` varchar(20) NOT NULL,
`cust_source` varchar(255) DEFAULT NULL,
`cust_industry` varchar(255) DEFAULT NULL,
`cust_level` varchar(255) DEFAULT NULL,
`cust_address` varchar(255) DEFAULT NULL,
`cust_phone` varchar(255) DEFAULT NULL,
PRIMARY KEY (`cust_id`),
UNIQUE KEY `UK_14ctafs86r97xwblaca93janm` (`cust_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
resources
下创建Hibernate核心配置文件hibernate.cfg.xml
<hibernate-configuration>
<session-factory>
<property name="connection.url">jdbc:mysql://localhost:3306/test?serverTimezone=GMT%2b8property>
<property name="connection.driver_class">com.mysql.jdbc.Driverproperty>
<property name="connection.username">rootproperty>
<property name="connection.password">rootproperty>
<property name="hibernate.connection.provider_class">org.hibernate.connection.C3P0ConnectionProviderproperty>
<property name="hibernate.c3p0.max_size">20property>
<property name="hibernate.c3p0.min_size">5property>
<property name="hibernate.c3p0.timeout">120property>
<property name="hibernate.c3p0.idle_test_period">3000property>
<property name="hibernate.show_sql">trueproperty>
<property name="hibernate.format_sql">trueproperty>
<property name="hibernate.dialect">org.hibernate.dialect.MySQLDialectproperty>
<property name="hibernate.hbm2ddl.auto">updateproperty>
<property name="hibernate.connection.autocommit">falseproperty>
<property name="hibernate.connection.isolation">4property>
<property name="hibernate.current_session_context_class">threadproperty>
<mapping resource="com/hb/domain/Customer.hbm.xml" />
session-factory>
hibernate-configuration>
配置连接池
需要引入对应的连接池驱动
<dependency>
<groupId>org.hibernategroupId>
<artifactId>hibernate-c3p0artifactId>
<version>5.0.7.Finalversion>
dependency>
Customer
(和数据库表映射的类)(这里使用了lombok生成get/set方法
)package com.hb.domain;
import lombok.Data;
@Data
public class Customer {
private Long cust_id;// '客户编号(主键)',
private String cust_name;// '客户名称(公司名称)',
private String cust_source;// '客户信息来源',
private String cust_industry;//'客户所属行业',
private String cust_level;// '客户级别',
private String cust_address;// '客户联系地址',
private String cust_phone;// '客户联系电话',
}
Customer
类的同级目录创建映射文件Customer.hbm.xml
<hibernate-mapping>
<class name="com.hb.domain.Customer" table="cst_customer">
<id name="cust_id" column="cust_id" type="java.lang.Long">
<generator class="native">generator>
id>
<property name="cust_name" column="cust_name" length="20" not-null="true" unique="true"/>
<property name="cust_source" column="cust_source" type="java.lang.String"/>
<property name="cust_industry">
<column name="cust_industry" length="255" sql-type="varchar(255)">column>
property>
<property name="cust_level" column="cust_level" type="java.lang.String">property>
<property name="cust_address" column="cust_address" type="java.lang.String">property>
<property name="cust_phone" column="cust_phone" type="java.lang.String">property>
class>
hibernate-mapping>
4 创建工具类HibernateUtils
package com.hb.utils;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
public class HibernateUtils {
static Configuration configuration = null;
static SessionFactory sessionFactory = null;
static {
// 加载一次配置文件
configuration = new Configuration();
configuration.configure();
// 获取一个sessionFactory
sessionFactory = configuration.buildSessionFactory();
}
// 从连接池获取的
public static Session openSession() {
return sessionFactory.openSession();
}
// 从当前线程中获取绑定的session
// 好处: 在多层之间调用方法获取的都是同一个session
public static Session getCurrentSession() {
/*特点: 1 默认是关闭的 需要配置开启
2 会自动给你关闭连接*/
Session session = sessionFactory.getCurrentSession();
return session;
}
}
获取管理hibernate的seesion对象就可以对数据库进crud了
import com.hb.domain.Customer;
import com.hb.utils.HibernateUtils;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.hibernate.query.Query;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
/**
* Hibernate的crud操作测试
*/
/*@RunWith(SpringRunner.class)*/
@SpringBootTest
public class TestHibernateCrud {
private static Session session;
private static Transaction tx;
/**
* 调用单元测试方法方法前的处理 获取会话/开启事务
*/
@Before
public void testBefore() {
System.out.println("openSession");
this.session = HibernateUtils.openSession();
System.out.println("session.beginTransaction");
this.tx = this.session.beginTransaction();
}
/**
* 调用单元测试方法方法前的处理 提交事务/关闭会话
*/
@After
public void testAfter() {
System.out.println("tx.commit");
this.tx.commit();
System.out.println("session.close");
this.session.close();
}
/**
* Hibernate新增
*/
@Test
public void testSave() {
Customer customer = new Customer();
customer.setCust_name("11111111");
this.session.save(customer);
}
/**
* Hibernate oid查询
*/
@Test
public void testOid() {
Customer customer = this.session.get(Customer.class, 6L);
System.out.println(customer);
}
/**
* Hibernate hql查询 下标
* 将HQL语句中的"?"改为JPA-style:
*/
@Test
public void testHSQLByIndex() {
// 条件查 类似sql语句的表达式 from 持久化类 where 属性=?
Query query = session.createQuery("FROM Customer WHERE cust_name like ?1 and cust_id=?2")
.setParameter(1, "h%")//模糊查询 h开头字符串
.setParameter(2, 2L);
List<Customer> list = query.list();
for (Customer customer : list) {
System.out.println(customer);
}
}
/**
* Hibernate hql查询 别名
*
* 别名:要求必须以冒号开头:
*/
@Test
public void testHSQLByAlias() {
// 条件查 类似sql语句的表达式 from 持久化类 where 属性=?
Query query = session.createQuery("FROM Customer WHERE cust_name like :custName and cust_id=:custId")
.setParameter("custName", "h%")//模糊查询 h开头字符串
.setParameter("custId", 2L);
List<Customer> list = query.list();
for (Customer customer : list) {
System.out.println(customer);
}
}
/*
hibernate 中createQuery与createSQLQuery两者区别是:
前者用的hql语句进行查询,后者可以用sql语句查询
前者以hibernate生成的Bean为对象装入list返回,后者则是以对象数组进行存储
*/
/**
* 通过SQL方式查询—下标
*/
@Test
public void testSQLByIndex() {
// 单列查 返回是Object
Query query = this.session.createSQLQuery("select * from cst_customer where cust_name like ? and cust_id = ?")
.setParameter(1, "h%")//模糊查询 h开头字符串
.setParameter(2, 2L);
List<Object> list = query.list();
for (Object object : list) {
System.out.println(object);
}
}
/**
* 通过SQL方式查询—下标
*/
@Test
public void testSQLByIndex2() {
// 单列查 返回是Object
Query query = this.session.createSQLQuery("select * from cst_customer where cust_name like ?1 and cust_id = ?2")
.setParameter(1, "h%")//模糊查询 h开头字符串
.setParameter(2, 2L);
List<Object> list = query.list();
for (Object object : list) {
System.out.println(object);
}
}
/**
* 通过SQL方式查询—别名
*/
@Test
public void testSQLByAlias() {
// 单列查 返回是Object
Query query = this.session.createSQLQuery("select * from cst_customer where cust_name like :custName and cust_id =:custId")
.setParameter("custName", "h%")//模糊查询 h开头字符串
.setParameter("custId", 2L);
List<Object> list = query.list();
for (Object object : list) {
System.out.println(object);
}
}
}
如果使用 spring-boot-starter-data-jpa ,底层内置了Hibernate,不需要引入关于Hibernate的jar包
<properties>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
<project.hibernate.version>5.0.7.Finalproject.hibernate.version>
properties>
<dependencies>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
<scope>testscope>
dependency>
<dependency>
<groupId>org.hibernategroupId>
<artifactId>hibernate-entitymanagerartifactId>
<version>${project.hibernate.version}version>
dependency>
<dependency>
<groupId>org.hibernategroupId>
<artifactId>hibernate-c3p0artifactId>
<version>${project.hibernate.version}version>
dependency>
<dependency>
<groupId>log4jgroupId>
<artifactId>log4jartifactId>
<version>1.2.17version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>5.1.6version>
dependency>
dependencies>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
version="2.0">
<persistence-unit name="myJpa" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.jpa.HibernatePersistenceProviderprovider>
<properties>
<property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver" />
<property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/test?characterEncoding=utf-8" />
<property name="javax.persistence.jdbc.user" value="root" />
<property name="javax.persistence.jdbc.password" value="root" />
<property name="hibernate.show_sql" value="true" />
<property name="hibernate.format_sql" value="true" />
<property name="hibernate.hbm2ddl.auto" value="update" />
properties>
persistence-unit>
persistence>
数据表(cst_customer)
和 实体类(Customer)
的映射package com.jpa.domain;
import lombok.Data;
import javax.persistence.*;
import java.io.Serializable;
/**
* @Entity 作用:指定当前类是实体类。
*
* @Table 作用:指定实体类和表之间的对应关系。
* 属性:name:指定数据库表的名称
*
* @Id 作用:指定当前字段是主键。
* @GeneratedValue 作用:指定主键的生成方式。。
* 属性:strategy :指定主键生成策略。
*
* @Column 作用:指定实体类属性和数据库表之间的对应关系
* 属性:
* name:指定数据库表的列名称。
* unique:是否唯一
* nullable:是否可以为空
* inserttable:是否可以插入
* updateable:是否可以更新
* columnDefinition: 定义建表时创建此列的DDL
* secondaryTable: 从表名。如果此列不建在主表上(默认建在主表),该属性定义该列所在从表的名字搭建开发环境[重点]
*/
@Data
@Entity //声明该类是和数据库表映射的实体类
@Table(name = "cst_customer") //建立实体类与表的映射关系
public class Customer implements Serializable {
private static final long serialVersionUID = -4422124275710090220L;
@Id //声明当前私有属性为主键
@GeneratedValue(strategy = GenerationType.IDENTITY) //配置主键的生成策略,为自增主键
@Column(name = "cust_id")
private Long custId;
@Column(name = "cust_name") //指定和表中cust_name字段的映射关系
private String custName;
@Column(name = "cust_source") //指定和表中cust_source字段的映射关系
private String custSource;
@Column(name = "cust_industry")
private String custIndustry;
@Column(name = "cust_level")
private String custLevel;
@Column(name = "cust_address")
private String custAddress;
@Column(name = "cust_phone")
private String custPhone;
}
package com.jpa.utils;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
/**
* 解决实体管理器工厂的浪费资源和耗时问题
* 通过静态代码块的形式,当程序第一次访问此工具类时,创建一个公共的实体管理器工厂对象
*
* 第一次访问getEntityManager方法:经过静态代码块创建一个factory对象,再调用方法创建一个EntityManager对象
* 第二次方法getEntityManager方法:直接通过一个已经创建好的factory对象,创建EntityManager对象
*/
public class JpaUtils {
private static EntityManagerFactory entityManagerFactory;
static {
//1.加载配置文件,创建entityManagerFactory
entityManagerFactory = Persistence.createEntityManagerFactory("myJpa");
}
public static EntityManager getEntityManager() {
return entityManagerFactory.createEntityManager();
}
}
一组实体类与底层数据源之间进行 O/R 映射的管理
。它可以用来管理和更新 Entity Bean
, 根椐主键查找 Entity Bean, 还可以通过JPQL语句查询实体。方法说明:
getTransaction : 获取事务对象
persist : 保存操作
merge : 更新操作
remove : 删除操作
find(立即加载)/getReference(延迟加载) : 根据id查询
package com.test.springbootjpademo;
import com.jpa.domain.Customer;
import com.jpa.utils.JpaUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.boot.test.context.SpringBootTest;
import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
import java.util.Random;
@SpringBootTest
public class TestJpaCrud {
private static EntityManager em;
private static EntityTransaction tx;
/**
* 调用单元测试方法方法前的处理 获取EntityManager对象/开启事务
*/
@Before
public void testBefore() {
System.out.println("JpaUtils.getEntityManager");
//通过工具类来获取EntityManager对象
this.em = JpaUtils.getEntityManager();
System.out.println("em.getTransaction");
//获取事务对象
this.tx = this.em.getTransaction();
System.out.println("tx.begin");
//开启事务
this.tx.begin();
}
/**
* 调用单元测试方法方法后的处理 获取提交事务/关闭EntityManager对象
*/
@After
public void testAfter() {
System.out.println("tx.commit");
this.tx.commit(); //提交事务
System.out.println("em.close");
this.em.close(); //释放资源
}
/**
* 保存操作
*/
@Test
public void testPersist() {
//完成增删改查操作,保存一个用户到数据库中
Customer customer = new Customer();
customer.setCustName("授课");
customer.setCustIndustry("教育");
try {
//保存操作
em.persist(customer);
} catch (Exception e) {
System.out.println("tx.rollback");
// 回滚事务
tx.rollback();
e.printStackTrace();
}
}
/**
* 保存一个实体
*/
@Test
public void testPersist2() {
try {
Random random = new Random();
for (int i = 0; i < 100; i++) {
//int num = random.nextInt(10000000);
Thread.sleep(50);
Long num = System.currentTimeMillis();
// 定义对象
Customer c = new Customer();
c.setCustName("李家沟" + num);
c.setCustLevel("VIP客户" + num);
c.setCustSource("网络"+ num);
c.setCustIndustry("教育"+ num);
c.setCustAddress("昌平区北七家镇" + num);
c.setCustPhone("010-" + num);
// 执行操作
em.persist(c);
}
} catch (Exception e) {
// 回滚事务
tx.rollback();
e.printStackTrace();
}
}
/**
* 修改操作
*/
@Test
public void testMerge() {
try {
Customer c1 = em.find(Customer.class, 3L);
c1.setCustName("江苏NIUBIU学院");
em.clear();//把c1对象从缓存中清除出去
em.merge(c1);
} catch (Exception e) {
//回滚事务
tx.rollback();
e.printStackTrace();
}
}
/**
* 删除操作
*/
@Test
public void testRemove() {
try {
Customer c1 = em.find(Customer.class, 5L);
em.remove(c1);
} catch (Exception e) {
// 回滚事务
tx.rollback();
e.printStackTrace();
}
}
/**
* 查询操作find()
*/
@Test
public void testGetOne() {
try {
// 执行操作
Customer c1 = em.find(Customer.class, 3L);
System.out.println(c1); // 输出查询对象
} catch (Exception e) {
// 回滚事务
tx.rollback();
e.printStackTrace();
}
}
}
JPQL(java持久化查询语言)
与Hibernate的Hsql差不多
package com.test.springbootjpademo;
import com.jpa.domain.Customer;
import com.jpa.utils.JpaUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
import javax.persistence.Query;
import java.util.List;
/**
* 使用JPQL(java持久化查询语言)来查询数据-JPQL与hibernate的HQL方式类似。
*/
public class TestJpaJPsql {
private static EntityManager em;
private static EntityTransaction tx;
/**
* 调用单元测试方法方法前的处理 获取EntityManager对象/开启事务
*/
@Before
public void testBefore() {
System.out.println("JpaUtils.getEntityManager");
//通过工具类来获取EntityManager对象
this.em = JpaUtils.getEntityManager();
System.out.println("em.getTransaction");
//获取事务对象
this.tx = this.em.getTransaction();
System.out.println("tx.begin");
//开启事务
this.tx.begin();
}
/**
* 调用单元测试方法方法后的处理 获取提交事务/关闭EntityManager对象
*/
@After
public void testAfter() {
System.out.println("tx.commit");
this.tx.commit(); //提交事务
System.out.println("em.close");
this.em.close(); //释放资源
}
/**
* 查询全部的客户
*
* TODO: 直接from查询出来的是一个映射对象,即:查询整个映射对象所有字段
*/
@Test
public void findAll() {
try {
String jpql = " from Customer";
//创建query对象
Query query = em.createQuery(jpql);
//使用query对象查询客户信息
List list = query.getResultList(); //查询所有的客户
for (Object object : list) {
System.out.println(object);
}
} catch (Exception e) {
//发生异常进行回滚
System.out.println("tx.rollback");
tx.rollback();
e.printStackTrace();
}
}
/**
* 查询全部的客户指定字段
*/
@Test
public void findAllField() {
try {
String jpql = "select custId,custName,custAddress from Customer";
//创建query对象
Query query = em.createQuery(jpql);
//使用query对象查询客户信息
List list = query.getResultList(); //查询所有的客户
for (Object object : list) {
System.out.println(object);
}
} catch (Exception e) {
//发生异常进行回滚
System.out.println("tx.rollback");
tx.rollback();
e.printStackTrace();
}
}
//分页查询客户
@Test
public void findPaged() {
try {
//创建query对象
String jpql = "from Customer";
Query query = em.createQuery(jpql);
//起始索引
query.setFirstResult(10);
//每页显示条数
query.setMaxResults(10);
//查询并得到返回结果
List list = query.getResultList(); //得到集合返回类型
for (Object object : list) {
System.out.println((Customer) object);
}
} catch (Exception e) {
// 回滚事务
tx.rollback();
e.printStackTrace();
}
}
//条件查询
// 通过数字占位符条件赋值
@Test
public void findConditionByIndex() {
try {
//创建query对象
String jpql = "from Customer where custName like ?1 ";
Query query = em.createQuery(jpql);
//对占位符赋值,从1开始
query.setParameter(1, "李家沟6225624%");
//查询并得到返回结果
Object object = query.getSingleResult(); //得到唯一的结果集对象
System.out.println((Customer) object);
} catch (Exception e) {
// 回滚事务
tx.rollback();
e.printStackTrace();
}
}
//条件查询
// 通过别名条件赋值
@Test
public void findConditionByAlias() {
try {
//创建query对象
String jpql = "from Customer where custName like :custName ";
Query query = em.createQuery(jpql);
//对占位符赋值,从1开始
query.setParameter("custName", "李家沟6225624%");
//查询并得到返回结果
Object object = query.getSingleResult(); //得到唯一的结果集对象
System.out.println((Customer) object);
} catch (Exception e) {
// 回滚事务
tx.rollback();
e.printStackTrace();
}
}
//根据客户id倒序查询所有客户
//查询所有客户
@Test
public void testOrder() {
try {
// 创建query对象
String jpql = "from Customer order by custId desc";
Query query = em.createQuery(jpql);
// 查询并得到返回结果
List list = query.getResultList(); // 得到集合返回类型
for (Object object : list) {
System.out.println((Customer) object);
}
} catch (Exception e) {
// 回滚事务
tx.rollback();
e.printStackTrace();
}
}
//统计查询
@Test
public void findCount() {
try {
// 查询全部客户
// 1.创建query对象
String jpql = "select count(custId) from Customer";
Query query = em.createQuery(jpql);
// 2.查询并得到返回结果
Object count = query.getSingleResult(); // 得到集合返回类型
System.out.println(count); //客户人数
} catch (Exception e) {
// 回滚事务
tx.rollback();
e.printStackTrace();
}
}
}
<properties>
<spring.version>4.2.4.RELEASEspring.version>
<hibernate.version>5.0.7.Finalhibernate.version>
<slf4j.version>1.6.6slf4j.version>
<log4j.version>1.2.12log4j.version>
<c3p0.version>0.9.1.2c3p0.version>
<mysql.version>5.1.6mysql.version>
properties>
<dependencies>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.9version>
<scope>testscope>
dependency>
<dependency>
<groupId>org.aspectjgroupId>
<artifactId>aspectjweaverartifactId>
<version>1.6.8version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-aopartifactId>
<version>${spring.version}version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>${spring.version}version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-context-supportartifactId>
<version>${spring.version}version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-ormartifactId>
<version>${spring.version}version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-beansartifactId>
<version>${spring.version}version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-coreartifactId>
<version>${spring.version}version>
dependency>
<dependency>
<groupId>org.hibernategroupId>
<artifactId>hibernate-coreartifactId>
<version>${hibernate.version}version>
dependency>
<dependency>
<groupId>org.hibernategroupId>
<artifactId>hibernate-entitymanagerartifactId>
<version>${hibernate.version}version>
dependency>
<dependency>
<groupId>org.hibernategroupId>
<artifactId>hibernate-validatorartifactId>
<version>5.2.1.Finalversion>
dependency>
<dependency>
<groupId>c3p0groupId>
<artifactId>c3p0artifactId>
<version>${c3p0.version}version>
dependency>
<dependency>
<groupId>log4jgroupId>
<artifactId>log4jartifactId>
<version>${log4j.version}version>
dependency>
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-apiartifactId>
<version>${slf4j.version}version>
dependency>
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-log4j12artifactId>
<version>${slf4j.version}version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>${mysql.version}version>
dependency>
<dependency>
<groupId>org.springframework.datagroupId>
<artifactId>spring-data-jpaartifactId>
<version>1.9.0.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-testartifactId>
<version>4.2.4.RELEASEversion>
dependency>
<dependency>
<groupId>javax.elgroupId>
<artifactId>javax.el-apiartifactId>
<version>2.2.4version>
dependency>
<dependency>
<groupId>org.glassfish.webgroupId>
<artifactId>javax.elartifactId>
<version>2.2.4version>
dependency>
dependencies>
SpringBoot项目引入SpringBoot-web和SpringBooDataJpa等基本包后就不需要引入这么多依赖
applicationContext.xml
,使用spring来管理
<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">
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver" />
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test?characterEncoding=utf-8" />
<property name="user" value="root" />
<property name="password" value="root" />
bean>
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="packagesToScan" value="com.springData.entity" />
<property name="persistenceProvider">
<bean class="org.hibernate.jpa.HibernatePersistenceProvider" />
property>
<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" />
<property name="showSql" value="true" />
bean>
property>
<property name="jpaDialect">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaDialect" />
property>
bean>
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory" />
bean>
<jpa:repositories base-package="com.springData.dao"
transaction-manager-ref="transactionManager"
entity-manager-factory-ref="entityManagerFactory">jpa:repositories>
<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>
<aop:config>
<aop:pointcut id="pointcut" expression="execution(* com.springData.service.*.*(..))" />
<aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut" />
aop:config>
<context:component-scan base-package="com.springData">context:component-scan>
beans>
数据表(cst_customer)
和 实体类(Customer)
的映射package com.springData.entity;
import lombok.Data;
import javax.persistence.*;
import java.io.Serializable;
/**
* @Entity 作用:指定当前类是实体类。
*
* @Table 作用:指定实体类和表之间的对应关系。
* 属性:name:指定数据库表的名称
*
* @Id 作用:指定当前字段是主键。
* @GeneratedValue 作用:指定主键的生成方式。。
* 属性:strategy :指定主键生成策略。
*
* @Column 作用:指定实体类属性和数据库表之间的对应关系
* 属性:
* name:指定数据库表的列名称。
* unique:是否唯一
* nullable:是否可以为空
* inserttable:是否可以插入
* updateable:是否可以更新
* columnDefinition: 定义建表时创建此列的DDL
* secondaryTable: 从表名。如果此列不建在主表上(默认建在主表),该属性定义该列所在从表的名字搭建开发环境[重点]
*/
@Data
@Entity //声明该类是和数据库表映射的实体类
@Table(name = "cst_customer") //建立实体类与表的映射关系
public class Customer implements Serializable {
private static final long serialVersionUID = -4422124275710090220L;
@Id //声明当前私有属性为主键
@GeneratedValue(strategy = GenerationType.IDENTITY) //配置主键的生成策略,为自增主键
@Column(name = "cust_id")
private Long custId;
@Column(name = "cust_name") //指定和表中cust_name字段的映射关系
private String custName;
@Column(name = "cust_source") //指定和表中cust_source字段的映射关系
private String custSource;
@Column(name = "cust_industry")
private String custIndustry;
@Column(name = "cust_level")
private String custLevel;
@Column(name = "cust_address")
private String custAddress;
@Column(name = "cust_phone")
private String custPhone;
}
JpaRepository
和JpaSpecificationExecutor
接口package com.springData.dao;
import com.springData.entity.Customer;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
/**
* 定义一个dao层接口,此接口只需要继承JpaRepository 和 JpaSpecificationExecutor接口即可,该接口就具备了增删改 * 查和分页等功能。
* JpaRepository<实体类类型,主键类型>:完成基本的CRUD操作
* JpaSpecificationExecutor<实体类类型>:用于复杂查询(分页等查询操作)
*/
public interface CustomerDao extends JpaRepository<Customer,Long> , JpaSpecificationExecutor<Customer> {
}
package com.test.springbootjpademo;
import com.springData.dao.CustomerDao;
import com.springData.entity.Customer;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Example;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.Optional;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:applicationContext.xml")
public class TestSpringDataJpa {
@Autowired
private CustomerDao customerDao;
/**
* 保存操作:调用save()方法
*/
@Test
public void testSave() {
Customer customer = new Customer();
customer.setCustName("黑马");
//保存
customerDao.save(customer);
}
/**
* 修改客户信息:调用save()方法
* 对于save():如果执行此方法时对象中存在id属性,即为更新操作,会根据id查询,再更新
* 如果此方法中不存在id属性,则为保存操作
*/
@Test
public void testUpdate() {
Customer customerParam = new Customer();
customerParam.setCustId(1L);
Example<Customer> example = Example.of(customerParam);
//先根据id查询id为1的客户
Optional<Customer> optionalCustomer = customerDao.findOne(example);
System.out.println(optionalCustomer.get());
//修改客户名称
optionalCustomer.get().setCustName("授课123");
System.out.println(optionalCustomer.get());
//更新
customerDao.save(optionalCustomer.get());
}
/**
* 删除操作
*/
@Test
public void testDelete() {
Customer customerParam = new Customer();
customerParam.setCustId(8L);
customerDao.delete(customerParam);
}
/**
* 根据id查询:调用findOne()
*/
@Test
public void testFindById() {
Customer customerParam = new Customer();
customerParam.setCustId(1L);
Example<Customer> example = Example.of(customerParam);
//先根据id查询id为1的客户
Optional<Customer> optionalCustomer = customerDao.findOne(example);
System.out.println(optionalCustomer.get());
}
}
1.传统的JDBC
//传统的JDBC :connection(连接) Statement(执行者) ResultSet(结果集)
//1. 获取链接
String url = "jdbc:mysql:localhost:3306/数据库的名称"
String user ="root";
String password = "";
String diverClass = "com.mysql.jadb.Driver";
Connection con = DiverManager.getConnection(url,user,password);
//2. 编写sql 获取语句执行者
PreparesStatement pre = conn.prepareStatement(sql);
//3. 语句执行,操作结果集ResultSet
//4. 释放资源
//Dao 先写接口再写实现
2. Spring 中使用 JdbcTemplate
//1. 添加相关的依赖 ; 2.配置数据源DataSource 3. 注入JdbcTemplate
//实际上就是将 连接的获取管理资源的释放等都交给spring 进行管理,在bean 配置文件中配置JdbcTemplate 并且,在使用的时候获取
//使用:
JdbcTemplaye jdbcTemplate = ctx.getBean("JdbcTemplate");
String sql = "select * from tableName"; sql 语句
//jdbcTemplate.各种执行方式: 在执行方式中写入参数和结果集的处理
“自动依赖模块”
:spring-boot-starter-data-jpa,只需要引入这个starter,application.properties/yml加上相应的配置即可使用<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-jpaartifactId>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
dependency>
2 . 在springBoot的 application.properties
添加配置信息
#mysql
# 默认端口
server.port=8443
# 数据库四要素
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/TEST?characterEncoding=UTF-8
spring.datasource.username=root
spring.datasource.password=root
# 配置是否自动创建数据库表
spring.jpa.generate-ddl=false
#指定数据库类型
spring.jpa.database=mysql
#显示sql语句
spring.jpa.show-sql=true
#数据库方言:支持的特有语法
spring.jpa.database-platform=org.hibernate.dialect.MySQL55Dialect
# 在springboot项目中使用springdata Jpa,并且希望当我的实体类发生了更改,数据库表结构随着实体类的改变而自动做出相应的改变。
spring.jpa.hibernate.ddl-auto=update
# 开启懒加载,不开启在实体类中使用 FetchType.LAZY 会报错
spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true
jpa.hibernate.ddl-auto
是hibernate的配置属性,其主要作用是:自动创建、更新、验证数据库表结构。
该参数的几种配置如下:
- create: 每次加载hibernate时都会删除上一次的生成的表,然后根据你的model类再重新来生成新表,哪怕两次没有任何改变也要这样执行,这就是导致数据库表数据丢失的一个重要原因。
- create-drop: 每次加载hibernate时根据model类生成表,但是sessionFactory一关闭,表就自动删除。
- update: 最常用的属性,第一次加载hibernate时根据model类会自动建立起表的结构(前提是先建立好数据库),以后加载hibernate时根据model类自动更新表结构,即使表结构改变了但表中的行仍然存在不会删除以前的行。要注意的是当部署到服务器后,表结构是不会被马上建立起来的,是要等应用第一次运行起来后才会。
- validate: 每次加载hibernate时,验证创建数据库表结构,只会和数据库中的表进行比较,不会创建新表,但是会插入新值。
//注解:
@Id(主键)
@GeneratedValue(主键的生成策略): 一般使用strategy = GenerationType.IDENTITY 主键自增
@Entity: 表明这个类要对数据表做一个映射
@Table(表名): 对应数据表的名字
@Colum: 可以对字段作相应的要求,一般是
@OneToOne: 表示一对一关系
@OneToMany: 表示一对多关系
@ManyToOne: 表示多对一关系
@ManyToMant: 表示多对多关系
//xxxRepository 的继承方式有两种
//1. 继承JpaRepository
xxxRepository extends JpaRepository<类名,表的主键在类中的类型>{
//重点在这个抽象类的编写
}
//2. 继承Repository
xxxRepository extends Repository<类名,表的主键在类中的类型>{
//重点在这个抽象类的编写
}
//重点都是编写这个接口中的抽象方法
这两种继承方式不一样的是:
extends Repository: Repository
是一个空的接口,里面没有任何内容,是一个标识接口,表明的继承他的xxxRepository 会被Springboot 管理,才能实现写抽象方法可以使用。extends jpaRepository: jpaRepository
是Repository 的一个子接口,里面定义了一些方法,在使用这些方法的时候回出现一些局限性。这里的局限性主要针对JpaRepository 中已经定义好的一些方法,有一些返回值,或者是异常处理的局限性。比如:findById(Integer id) 的返回值类型:Optional;getOne(Integer id) 如果查找不到id 就会直接报错
1.创建接口
public interface UserRepository {
public Users findUserById(Integer userid);
}
2.创建接口实现类
/**
* 用户自定义Repository接口讲解
*/
public interface UsersDao extends JpaRepository<Users, Integer>,JpaSpecificationExecutor<Users>,UserRepository{
}
3.创建接口实现类
public class UsersDaoImpl implements UserRepository {
@PersistenceContext(name="entityManagerFactory")
private EntityManager em;
@Override
public Users findUserById(Integer userid) {
System.out.println("MyRepository......");
return this.em.find(Users.class, userid);
}
}
4.测试
/**
* JpaRepository接口测试
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class RepositoryTest {
@Autowired
private UsersDao usersDao;
/**
* 需求:根据用户ID查询数据
*/
@Test
public void test6(){
Users users = this.usersDao.findUserById(5);
System.out.println(users);
}
}
注解 | 说明 |
---|---|
@Entity | 标识实体类是JPA实体,告诉JPA在程序运行时生成实体类对应表 |
@Table | 设置实体类在数据库所对应的表名 |
@Basic | 表示简单属性到数据库表字段的映射(几乎不用) |
@Embedded | 指定类或它的值是一个可嵌入的类的实例的实体的属性。 |
@Id | 标识类里所在变量为主键 |
@GeneratedValue | 设置主键生成策略,此方式依赖于具体的数据库: 指定如何标识属性可以被初始化,例如自动、手动、或从序列表中获得的值。 |
@Transient | 表示属性并非数据库表字段的映射,ORM框架将忽略该属性 |
@Temporal | 当我们使用到java.util包中的时间日期类型,则需要@Temporal注释来说明转化成java.util包中的类型。其中包含三种转化类型分别是: java.sql.Date日期型,精确到年月日,例如“2008-08-08” java.sql.Time时间型,精确到时分秒,例如“20:00:00” java.sql.Timestamp时间戳,精确到纳秒,例如“2008-08-08 20:00:00.000000001” |
@Column | 表示对这个变量所对应的字段名进行一些个性化的设置,例如字段的名字,字段的长度,是否为空和是否唯一等等设置。 |
@Enumerated | 使用此注解映射枚举字段,以String类型存入数据库 注入数据库的类型有两种: EnumType.ORDINAL(Interger) EnumType.STRING(String) |
@SequenceGenerator | 指定在@GeneratedValue注解中指定的属性的值。它创建了一个序列。 |
@TableGenerator | 指定在@GeneratedValue批注指定属性的值发生器。它创造了的值生成的表。 |
@AccessType | 这种类型的注释用于设置访问类型。如果设置@AccessType(FIELD),则可以直接访问变量并且不需要getter和setter,但必须为public。如果设置@AccessType(PROPERTY),通过getter和setter方法访问Entity的变量。 |
@JoinColumn | 指定一个实体组织或实体的集合。这是用在多对一和一对多关联。 |
@UniqueConstraint | 指定的字段和用于主要或辅助表的唯一约束。 |
@ColumnResult | 参考使用select子句的SQL查询中的列名。 |
@ManyToMany | 定义了连接表之间的多对多一对多的关系。 |
@ManyToOne | 定义了连接表之间的多对一的关系。 |
@OneToMany | 定义了连接表之间存在一个一对多的关系。 |
@OneToOne | 定义了连接表之间有一个一对一的关系。 |
@NamedQueries | 指定命名查询的列表。 |
@NamedQuery | 指定使用静态名称的查询。 |
@CreatedDate、@CreatedBy、@LastModifiedDate、@LastModifiedBy | 表示字段为 创建时间字段(insert自动设置) 创建用户字段(insert自动设置) 最后修改时间字段(update自定设置 最后修改用户字段(update自定设置) |
@Embedded、@Embeddable | 当一个实体类要在多个不同的实体类中进行使用,而其不需要生成数据库表 @Embeddable:注解在类上,表示此类是可以被其他类嵌套 @Embedded:注解在属性上,表示嵌套被@Embeddable注解的同类型类 |
@ElementCollection | 集合映射 |
@MappedSuperclass | 实现将实体类的多个属性分别封装到不同的非实体类中 1.注解的类将不是完整的实体类,不会映射到数据库表,但其属性将映射到子类的数据库字段 2.注解的类不能再标注@Entity或@Table注解,也无需实现序列化接口 3.注解的类继承另一个实体类 或 标注@MappedSuperclass类,他可使用@AttributeOverride 或 @AttributeOverrides注解重定义其父类属性映射到数据库表中字段。 |
@Entity
@Table
不一致
时需要使用 @Table 标注说明,该标注与 @Entity 标注并列使用catalog 和 schema
用于设置表所属的数据库目录或模式,通常为数据库名。@Column
不一致
时需要用@Column 说明,还可与 @Id 标注一起使用。常用属性是 name
,用于设置映射数据库表的列名。其他属性如:unique 、nullable、length
等。通常 ORM 框架可以根据属性类型自动判断数据库中字段的类型
,但是对于Date类型仍无法确定数据库中字段类型究竟是DATE,TIME还是TIMESTAMP.此外, String的默认映射类型为VARCHAR
, 可以将 String 类型映射到特定数据库的 BLOB 或TEXT 字段类型.
@Id
//定义主键,生成主键的策略AUTO自动的根据数据的类型生成主键
@GeneratedValue(strategy=GenerationType.AUTO)
@Id //定义数据列
// @Column(name="ID")//定义数据库的列名如果与字段名一样可以省略
public Integer getId() {
return id;
}
@GeneratedValue
主键的生成策略
,通过strategy
属性指定。默认情况下,JPA 自动选择一个最适合底层数据库的主键生成策略:
identity
,auto increment
。在javax.persistence.GenerationType
中定义了以下几种可供选择的策略:
IDENTITY
:采用数据库 ID自增长的方式来自增主键字段,Oracle 不支持这种方式;AUTO
: JPA自动选择合适的策略,是默认选项
;SEQUENCE
:通过序列产生主键,通过@SequenceGenerator
注解指定序列名,MySql 不支持这种方式TABLE
:通过表产生主键,框架借由表模拟序列产生主键,使用该策略可以使应用更易于数据库移植。@Basic
getXxxx()方法
,默认 即为 @Basic
EAGER和LAZY
两种,分别表示主支抓取
和延迟加载
,默认为EAGER.
FetchType.LAZY
:懒加载
,加载一个实体时,定义懒加载的属性不会马上从数据库中加载。FetchType.EAGER
:急加载
,加载一个实体时,定义急加载的属性会立即从数据库中加载。@Temporal
在核心的 Java API 中并没有定义 Date 类型的精度(temporal precision). 而在数据库中,表示 Date 类型的数据有 DATE, TIME, 和 TIMESTAMP
三种精度(即单纯的日期,时间,或者两者 兼备).
@Temporal(TemporalType.TIMESTAMP)// 时间戳
public Date getCreatedTime() {
return createdTime;
}
@Temporal(TemporalType.DATE) //时间精确到天
public Date getBirth() {
return birth;
}
注解 | 说明 |
---|---|
@JsonFormat | .@JsonFormat(pattern=“yyyy-MM-dd HH:mm:ss”,timezone=“GMT+8”):将Date属性转换为String类型, timezone解决(相差8小时) |
@JsonSerialize | 作用在类或字段上,转化java对象到json格式(需自定义转化类继承JsonSerializer) |
@JsonDeserialize | 作用在类或字段上,转化json格式到java对象(需自定义转化类继承JsonDeserializer) |
@JsonProperty | 作用在属性上,把属性名称序列化为另一个名称(trueName属性序列化为name) |
@JsonIgnoreProperties(ignoreUnknown = true) | 作用在类上,忽略掉json数据里包含了实体类没有的字段 |
@JsonIgnore | 在json序列化时将java bean中的一些属性忽略掉,序列化和反序列化都受影响 |
使用 JPA 进行数据持久化有两种实现方式。
Repository 接口
是 Spring Data JPA 中为我我们提供的所有接口中的顶层接口 Repository 提供了两种查询方式的支持
继承树:
Repository->CrudRepository->PagingAndSortingRepository->JpaRepository->xxxRepository
CrudRepository : 定义了基本的crud 操作,接口如下
package org.springframework.data.repository;
import java.io.Serializable;
@NoRepositoryBean
public interface CrudRepository<T, ID extends Serializable> extends Repository<T, ID> {
<S extends T> S save(S entity); // 保存并返回(修改后的)实体
<S extends T> Iterable<S> save(Iterable<S> entities); //保存并返回(修改后的)实体集合(批量保存)
T findOne(ID id); // 根据ID获取实体
boolean exists(ID id); // 判断指定ID的实体是否存在
Iterable<T> findAll(); // 查询所有实体
Iterable<T> findAll(Iterable<ID> ids); // 根据ID集合查询实体
long count(); // 获取实体的数量
void delete(ID id); // 删除指定ID的实体
void delete(T entity); // 删除实体
void delete(Iterable<? extends T> entities); // 删除实体集合(批量删除)
void deleteAll(); // 删除所有实体
}
PagingAndSortingRepository继承于CrudRepository,除了具有CrudRepository接口的能力外,还新增了分页和排序的功能
接口如下:
package org.springframework.data.repository;
import java.io.Serializable;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
@NoRepositoryBean
public interface PagingAndSortingRepository<T, ID extends Serializable> extends CrudRepository<T, ID> {
Iterable<T> findAll(Sort sort); // 查询所有实体并排序(不带分页的排序)
Page<T> findAll(Pageable pageable); // 分页查询实体(带分页的排序)
}
//分页查询:
findAll(Pageable)
//构建Pageable
Pageable pageable = PageRequest.of(0,1);//springData2.0之前 用new PageRequest(0,1)
Page<User> page = userRepository.findAll(pageable);
//注释:
//PageRequest 是常用的Pageable 的子类
//springData2.0之前使用new构造方式创建分页对象
//构造函数:
public PageRequest(int page, int size) {} //:当前要获取那一页,每一页的数据数量
public PageRequest(int page, int size, Sort sort) {}//: 将当前页数据排序排序之后获取
//springData2.0之后使用PageRequest.of()方式创建分页对象
public static PageRequest of(int page, int size);
public static PageRequest of(int page, int size, Sort sort);
public static PageRequest of(int page, int size, Direction direction, String... properties);
//可以通过Page的方法获得很多信息:
page.getTotalElements(); //数据的总数
page.getTotalPages(); //页数
page.getNumber(); //当前的页数
page.getContent(); //当前页数的所有元素
page.getNumberOfElements(); //当前页的数据总数
//排序查询:
findAll(Sort sort)
//构建 Sort:
Sort.Order order = new Sort.Order(Sort.Direction.ASC,"id");
Sort sort = new Sort(order);
List<User> list = userRepository.findAll(sort);
//注释:Order的参数:Sort.Direction.ASC 升序;Sort.Direction.DESC 降序;排序的数据
JpaRepository开发时使用的最多的接口,继承于PagingAndSortingRepository
,所以它传递性地拥有了以上接口的所有方法,同时,它还继承了另外一个QueryByExampleExecutor
接口。其特点是可以帮助我们将其他接口的方法的返回值做适配处理。可以使得我们在开发时更方便的使用这些方法。
package org.springframework.data.jpa.repository;
import java.io.Serializable;
import java.util.List;
import javax.persistence.EntityManager;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.Sort;
import org.springframework.data.repository.NoRepositoryBean;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.query.QueryByExampleExecutor;
@NoRepositoryBean
public interface JpaRepository<T, ID extends Serializable>
extends PagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> {
List<T> findAll(); // 查询所有实体(不排序)
List<T> findAll(Sort sort); // 查询所有实体并排序
List<T> findAll(Iterable<ID> ids); // 根据ID集合查询实体
<S extends T> List<S> save(Iterable<S> entities); // 保存并返回(修改后的)实体集合(批量保存)
void flush(); // 提交事务(强制缓存与数据库同步)
<S extends T> S saveAndFlush(S entity); // 保存实体并立即提交事务
void deleteInBatch(Iterable<T> entities); // 批量删除实体集合(批量删除)
void deleteAllInBatch();// 批量删除所有实体(删除所有)
T getOne(ID id); // 根据ID查询实体
@Override
<S extends T> List<S> findAll(Example<S> example); // 查询与指定Example匹配的所有实体
@Override
<S extends T> List<S> findAll(Example<S> example, Sort sort);// 查询与指定Example匹配的所有实体并排序
}
以上的接口是一个自上而下的继承树,通常使用的是JpaRepository。
QueryByExampleExecutor接口允许开发者根据给定的样例执行查询操作,接口定义如下。
package org.springframework.data.repository.query;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
public interface QueryByExampleExecutor<T> {
<S extends T> S findOne(Example<S> example); // 查询与指定Example匹配的唯一实体
<S extends T> Iterable<S> findAll(Example<S> example); // 查询与指定Example匹配的所有实体
<S extends T> Iterable<S> findAll(Example<S> example, Sort sort); // 查询与指定Example匹配的所有实体并排序
<S extends T> Page<S> findAll(Example<S> example, Pageable pageable);// 分页查询与指定Example匹配的所有实体
<S extends T> long count(Example<S> example); // 查询与指定Example匹配的实体数量
<S extends T> boolean exists(Example<S> example); // 判断与指定Example匹配的实体是否存在
}
Example查询官方文档
JpaSpecificationExecutor
: 是一个单独的接口,不属于上面的继承关系,主要是为分页查询提供过滤条件
。在实际的开发中使用的也比较多,
JPA critice
查询条件,在这个接口中的方法中传入Specification
为分页定义条件源码如下:
public interface JpaSpecificationExecutor<T> {
/*根据查询条件返回一个实体*/
T findOne(Specification<T> spec);
/* 根据查询条件返回多个实体.*/
List<T> findAll(Specification<T> spec);
/*根据查询条件和分页参数,返回当前页的实体信息.*/
Page<T> findAll(Specification<T> spec, Pageable pageable);
/*根据查询条件和排序规则,返回一个排序好的实体集合. */
List<T> findAll(Specification<T> spec, Sort sort);
/**
*根据查询条件统计实体的数量 */
long count(Specification<T> spec);
}
Specification
JPA Criteria
查询条件。通常使用匿名内部类的方式来创建该接口的对象。源码如下:
public interface Specification<T> {
Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb);
}
使用
//1. xxxRepository 继承JpaRepository,JpaSpecificationExecutor
public interface UserRepositorySpecification extends JpaRepository<User,Integer>,JpaSpecificationExecutor<User>{}
//2. 在Service层中构建Specification
public class ImSpecificationService implements SpecificationService {
@Autowired
UserRepositorySpecification userRepositorySpecification;
//测试Specification
@Override
public void TestSpecification() {
Pageable pageable = PageRequest.of(0,1);//springData2.0之前 用new PageRequest(0,1)
//构建Specification
Specification<User> specification = new Specification<User>() {
/**
*
* @param root: 将User 对象映射为 Root 可以通过它的对象获取对象中的各种字段
* @param criteriaQuery :查询条件的容器,存放查询条件;存放单个条件或者条件数组
* @param criteriaBuilder :构造查询条件,大于/小于/等于/like,比较对象
* @return predicate :定义查询条件;条件和比较的对象
*/
@Override
public Predicate toPredicate(Root<User> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
Path path = root.get("id"); //查询过滤的对象,这里是id
//这个路径是 Root(User(id)),字段路径
//定义过滤条件
criteriaBuilder.gt(path,5);//条件是查询id >5 的数据进行分页
return criteriaBuilder.gt(path,5);
}
};
Page<User> page = userRepositorySpecification.findAll(specification,pageable);
}
}
创建接口
/**
* JpaSpecificationExecutor接口讲解
*注意:JpaSpecificationExecutor:不能单独使用,需要配合着jpa中的其他接口一起使用
*/
public interface UsersDao extends JpaRepository<Users, Integer>,JpaSpecificationExecutor<Users>{
}
测试接口
/**
* JpaRepository接口测试
* @author Administrator
*
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class RepositoryTest {
@Autowired
private UsersDao usersDao;
/**
* 需求:根据用户姓名查询数据
*/
@Test
public void test1(){
Specification<Users> spec = new Specification<Users>() {
/**
* @return Predicate:定义了查询条件
* @param Root root:根对象。封装了查询条件的对象
* @param CriteriaQuery> query:定义了一个基本的查询。一般不使用
* @param CriteriaBuilder cb:创建一个查询条件
*/
@Override
public Predicate toPredicate(Root<Users> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
Predicate pre = cb.equal(root.get("username"), "王五");
return pre;
}
};
List<Users> list = this.usersDao.findAll(spec);
for (Users users : list) {
System.out.println(users);
}
}
}
/**
* 多条件查询 方式一
* 需求:使用用户姓名以及年龄查询数据
*/
@Test
public void test2(){
Specification<Users> spec = new Specification<Users>() {
@Override
public Predicate toPredicate(Root<Users> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
List<Predicate> list = new ArrayList<>();
list.add(cb.equal(root.get("username"),"王五"));
list.add(cb.equal(root.get("userage"),24));
//此时条件之间是没有任何关系的。
Predicate[] arr = new Predicate[list.size()];
return cb.and(list.toArray(arr));
}
};
List<Users> list = this.usersDao.findAll(spec);
for (Users users : list) {
System.out.println(users);
}
}
/**
* 多条件查询 方式二
* 需求:使用用户姓名或者年龄查询数据
*/
@Test
public void test3(){
Specification<Users> spec = new Specification<Users>() {
@Override
public Predicate toPredicate(Root<Users> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
return cb.or(cb.equal(root.get("username"),"王五"),cb.equal(root.get("userage"), 25));
}
};
List<Users> list = this.usersDao.findAll(spec);
for (Users users : list) {
System.out.println(users);
}
}
/**
* 需求:查询王姓用户,并且做分页处理
*/
@Test
public void test4(){
//条件
Specification<Users> spec = new Specification<Users>() {
@Override
public Predicate toPredicate(Root<Users> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
return cb.like(root.get("username").as(String.class), "王%");
}
};
//分页
Pageable pageable = PageRequest.of(2, 2);//springData2.0之前 用new PageRequest(0,1)
Page<Users> page = this.usersDao.findAll(spec, pageable);
System.out.println("总条数:"+page.getTotalElements());
System.out.println("总页数:"+page.getTotalPages());
List<Users> list = page.getContent();
for (Users users : list) {
System.out.println(users);
}
}
/**
* 需求:查询数据库中王姓的用户,并且根据用户id做倒序排序
*/
@Test
public void test5(){
//条件
Specification<Users> spec = new Specification<Users>() {
@Override
public Predicate toPredicate(Root<Users> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
return cb.like(root.get("username").as(String.class), "王%");
}
};
//排序
Sort sort = new Sort(Direction.DESC,"userid");
List<Users> list = this.usersDao.findAll(spec, sort);
for (Users users : list) {
System.out.println(users);
}
/**
* 需求:查询数据库中王姓的用户,做分页处理,并且根据用户id做倒序排序
*/
@Test
public void test6(){
//排序等定义
Sort sort = new Sort(Direction.DESC,"userid");
//分页的定义
Pageable pageable = PageRequest.of(2,2, sort);//springData2.0之前 用new PageRequest(0,1)
//查询条件
Specification<Users> spec = new Specification<Users>() {
@Override
public Predicate toPredicate(Root<Users> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
return cb.like(root.get("username").as(String.class), "王%");
}
};
Page<Users> page = this.usersDao.findAll(spec, pageable);
System.out.println("总条数:"+page.getTotalElements());
System.out.println("总页数:"+page.getTotalPages());
List<Users> list = page.getContent();
for (Users users : list) {
System.out.println(users);
}
Specification查询官方文档
添加数据: xxxRepository.save(对象)
添加和修改
使用的都是save()
, 判断的依据是主键
,如果对象中没有赋值主键
那么就是添加
新的数据;如果对象中主键有值 那就是对这行数据的修改
。getOne(id)//根据主键查找数据,缺点:当数据库中没有相应的数据的时候会直接抛出异常,当你在 查找数据发现出现的错误莫名其妙 ,找不到错在有可能就是它
findById(id)// 返回值为:Optional<类> Puser = xxxRepository.findById(id);
//可以对返回值进行处理:
User user = null;
try{
user = Puser.get();
}catch(Exception ex){
//出现异常说明:Puser 中没有 user
}
//这样的获取就不会有异常抛出,user=Null 就表示没有查询得到数据
//使用场景:常用在用户登录;为了判断数据是否在数据库中,可能在也可能不在;如果是单纯的为了把数据拿
//出来用其实可以使用getOne(id)。前提是保证数据表中也一定有这个数据,在测试的时候要注意
//这两个方法都是JpaRepository 中写好的方法,换句话说如果你的repository 继承JpaRepository 使用时就要注意这样的问题
从Spring Data2.0
开始对于返回单个实例的CRUD方法可以使用java8Optional
接口作为方法返回值来可能存在的默认值,典型示例为CrudRepository
的findById
方法
另外Spring也提供了几个注解来处理Null值
@NonNullApi
: 在包级别使用来声明参数和返回值不能为Null@NonNull:
在参数或返回值上使用,当它们不能为Null时(如果在包级别上使用了@NonNullApi注解则没有必要再使用@NonNull注解了)@Nullable
: 在参数或返回值上使用,当它们可以为Null时Delete/Updata 操作实现:@Modifying / @Trasational
// 1. 在Repository中定义的方法上添加@Modifying 和 @Trasational
public interface UserRepository extends JpaRepository<User,Integer> {
@Transactional
@Modifying
@Query("delete from User u where u.name =?1")
void deleteUser(String name);
@Transactional()
@Modifying
@Query("update User u set u.password = ?2 where u.username = ?1")
int updatePasswordByUsername(String username, String password);
@Transactional()
@Modifying
@Query("delete from User where username = ?1")
void deleteByUsername(String username);
}
// 2.Repository添加@Modifying,在Serice 层对应的方法上添加注解 @Transational
// Repostory层:
@Modifying
@Query("delete from User u where u.name =?1")
void deleteUser(String name);
// Service层
@Transactional
@Override
public void TestModify(String name) {
//测试Modify
userRepository.deleteUser(name);
}
在@Query
注解中编写JPQL语句实现DELETE和UPDATA操作必须加上@Modifying
注解,以通知Spring DATA这是一个DELETE或者UPDATA操作。
@Modifying
声明这是一个DELETE或者UPDATA的操作;@Modifying
的返回值为:void 或者 int(表示改变的数据行数)
UPDATA 和DELETE 操作需要使用事务,必须定义service层
,在service层的方法上添加@Transactional
。在默认的情况下,JPA的事务会设置为只读@Transational(readOnly = true)
,就是不能对数据库进行任何修改
, 如果Query语句在执行的时候出现问题,将会回滚到执行前的状态
@Transational(readOnly = false)
事务一般在service 层中进行处理
SPEL表达式
在原生SQL语句
中使用(从Spring Data JPA版本1.4开始使用)
//#{#entityName} 值为'Book'对象对应的数据表名称(book)。
@Query(value = "select * from #{#entityName} b where b.name=?1", nativeQuery = true)
#{#entityName}
获取到的是实体类的类名。,使用@Entity注解标注实体类Book
,Spring会将实体类Book纳入管理。默认·#{#entityName}·的值就是Book
·。
@Entity(name = “book”)
来标注实体类Book,此时#{#entityName}
的值就变成了book
@Entity
来标注实体类时指定name等于实体类对应的表名。在原生sql语句
中,就可以把#{#entityName}
来作为数据表名使用。除了可以直接使用Spring Data JPA接口已经实现的增删改查方法外,Spring Data JPA还允许开发者自定义查询方法
,符合SpringDataJPA命名规则的方法
,Spring Data JPA能够根据其方法名为其自动生成SQL
,还支持的关键字有:find,query、get、read、count、delete、remove
等。
find…By, read…By, query…By, count…By以及get…By
截取掉,然后对剩下部分进行解析,第一个By会被用作分隔符来指示实际查询条件的开始。 我们可以在实体属性上定义条件,并将它们与And和Or连接起来,从而创建不同的查询。规范的方法名不仅能完成查找还能完成count 操作
在Repository 子接口中声明方法:
- 方法名需要符合一定规范,如: 查询方法以
find | read | get ...
开头,删除方法以delete|remove
开头- 涉及条件查询时,条件的属性用条件关键字连接
- 要注意的是:条件属性以首字母大写
命名规则
** findBy(关键字) + 属性名称(属性名称的首字母大写) + 查询条件(首字母大写)**
连接查询条件关键字 | 方法命名 | 对应的sql where字句 |
---|---|---|
And | findByNameAndPwd | where name= ?1 and pwd =?2 |
Or | findByNameOrSex | where name= ?1 or sex=?2 |
Is,Equals | findById,findByIdEquals | where id= ?1 |
Between | findByIdBetween | where id between ?1 and ?2 |
LessThan | findByIdLessThan | where id < ?1 |
LessThanEquals | findByIdLessThanEquals | where id <= ?1 |
GreaterThan | findByIdGreaterThan | where id > ?1 |
GreaterThanEquals | findByIdGreaterThanEquals | where id > = ?1 |
After | findByIdAfter | where id > ?1 |
Before | findByIdBefore | where id < ?1 |
IsNull | findByNameIsNull | where name is null |
isNotNull,NotNull | findByNameNotNull | where name is not null |
Like | findByNameLike | where name like ? |
NotLike | findByNameNotLike | where name not like ?1 |
StartingWith | findByNameStartingWith | where name like ‘?1%’ |
EndingWith | findByNameEndingWith | where name like ‘%?1’ |
Containing | findByNameContaining | where name like ‘%?1%’ |
OrderBy | findByIdOrderByAgeDesc | where id=?1 order by age=?2 desc |
Not | findByNameNot | where name <> ?1 |
In | findByIdIn(Collection> c) | where id in (?1) |
NotIn | findByIdNotIn(Collection> c) | where id not in (?1) |
True | findByActiveTrue() | where active= true |
False | findByActiveFalse() | where active= false |
IgnoreCase | findByNameIgnoreCase | where UPPER(name)=UPPER(?1) |
另外,Spring Data JPA 还提供了对分页查询、自定义SQL、查询指定N条记录、联表查询等功能的支持,以员工实体资源库接口EmployeeRepository为例,功能代码示意如下。
@Repository
public interface EmployeeRepository extends JpaRepository<Employee, Long> {
//根据部门ID获取员工数量
int countByDepartmentId(Long departmentId);
//根据部门ID分页查询
Page<Employee> queryByDepartmentId(Long departmentId, Pageable pageable);
// 根据员工ID升序查询前10条
List<Employee> readTop10ByOrderById();
//根据员工姓名取第一条记录
Employee getFirstByName(String name, Sort sort);
//连表查询
@Query("select e.id as employeeId,e.name as employeeName,d.id as departmentId,d.name as departmentName from Employee e , Department d where e.id= ?1 and d.id= ?2")
EmployeeDetail getEmployeeJoinDepartment(Long eid, Long did);
//修改指定ID员工的姓名
@Modifying
@Transactional(timeout = 10)
@Query("update Employee e set e.name = ?1 where e.id = ?2")
int modifyEmployeeNameById(String name, Long id);
//删除指定ID的员工
@Transactional(timeout = 10)
@Modifying
@Query("delete from Employee where id = ?1")
void deleteById(Long id);
}
@Query
用于编写方法名命名规则查询无法完成的查询语句@Query
注解,只需在声明的方法上面标注该注解,同时提供一个 JPQL 查询语句就行了nativeQuery = true
, 可以在@Query
注解中编写原生的SQL语句
//一.索引参数:
@Query("SELECT p FROM Person p WHERE p.lastName = ?1 AND p.email = ?2")
List<Person> testQueryAnnotationParams1(String lastName, String email);
//?1,?2 是占位符,需要方法传递的参数顺序与其保持一致
//二.命名参数:
@Query("SELECT p FROM Person p WHERE p.lastName = :lastName AND p.email = :email")
List<Person> testQueryAnnotationParams2(@Param("email") String email, @Param("lastName") String lastName);
//三.含有like关键字的查询:
// 3.1.在占位符上添加%,在查询方法的参数中就不需要添加%
@Query("SELECT p FROM Person p WHERE p.lastName LIKE %?1% OR p.email LIKE %?2%")
List<Person> testQueryAnnotationLikeParam(String lastName, String email);
// 3.2.在命名参数上添加%,在查询方法的参数中就不需要添加%
@Query("SELECT p FROM Person p WHERE p.lastName LIKE %:lastName% OR p.email LIKE %:email%")
List<Person> testQueryAnnotationLikeParam3(@Param("email") String email, @Param("lastName") String lastName);
// 3.3.在传递的参数上添加%
@Query("SELECT p FROM Person p WHERE p.lastName LIKE ?1 OR p.email LIKE ?2")
List<Person> testQueryAnnotationLikeParam2(String lastName, String email);
// 传递的参数: “%A%” ,"%[email protected]%"
//四.使用原生的SQL 语句进行查询 设置 nativeQuery = true
@Query(value = "SELECT count(id) FROM jpa_persons", nativeQuery = true)
以上在@Query() 中写的类似sql 语句叫做 JPQL 是在JavaEE 中面向对象的查询语句,基于原生的SQL,JPQL 不支持INSERT,用save代替。但JPQL到SQL的转换无须开发者关心,JPQL解析器会负责完成这种转换,并负责执行这种转换的SQL语句来更新数据库。
使用原生的SQL语句.参数传递的方式还是可以使用占位符和命名参数
//使用原生的SQL 语句进行查询 设置 nativeQuery = true
@Query(value = "SELECT count(id) FROM jpa_persons", nativeQuery = true)
// 1.from 的后面接的 数据表名
// 2.参数传递的方式还是可以使用占位符和命名参数
在方法参数中直接传入Pageable
或Sort
可以完成动态分页或排序,通常Pageable或Sort
会是方法的最后一个参数,如:
@Query("select u from User u where u.username like %?1%")
Page<User> findByUsernameLike(String username, Pageable pageable);
@Query("select u from User u where u.username like %?1%")
List<User> findByUsernameAndSort(String username, Sort sort);
那调用repository方法时传入什么参数呢?
对于Pageable参数,在Spring Data 2.0
之前我们可以new一个org.springframework.data.domain.PageRequest
对象,现在这些构造方法已经废弃 ,取而代之Spring推荐我们使用PageRequest的of方法
new PageRequest(0, 5);
new PageRequest(0, 5, Sort.Direction.ASC, "username");
new PageRequest(0, 5, new Sort(Sort.Direction.ASC, "username"));
PageRequest.of(0, 5);
PageRequest.of(0, 5, Sort.Direction.ASC, "username");
PageRequest.of(0, 5, Sort.by(Sort.Direction.ASC, "username"));
注意:Spring Data PageRequest的page参数是从
0
开始的zero-based page index
对于Sort参数,同样可以new一个org.springframework.data.domain.Sort
,但推荐使用Sort.by
方法
@NamedQuery(或 @NamedNativeQuery)
定义好查询语句,唯一要做的就是为该语句命名时,需要满足”DomainClass.methodName()”
的 命名规则//1.编写持久层接口继承JpaRepository接口:
public interface FindUserByNamedQueryRepository extends JpaRepository<User, Integer> {
User findUserWithName(@Param("name") String name);
}
//2.编写实体类类:
@Entity
@NamedQueries(value={
@NamedQuery(name="User.findUserWithName",query="select u from User u where u.name = :name")
})
//省略以下是实体类......
注意:
@NamedQuery中的name属性的值要和接口中的方法名称一样。
如果只有一个方法,则可以使用@NamedQuery
,此处如果是多个方法,那么需要使用@NamedQueries
需求:用户与角色的一对一的关联关系
人=>一端=>一个人只有一个身份证
身份证=>一端=>一个身份证只能属于一个人
//---------------人类表---------------
CREATE TABLE `t_person` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`birthday` datetime DEFAULT NULL,
`name` varchar(20) DEFAULT NULL,
`sex` varchar(1) DEFAULT NULL,
`card_id` bigint(20) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `FKq7y12min0l1gij5ixjtyhxfks` (`card_id`),
CONSTRAINT `FKq7y12min0l1gij5ixjtyhxfks` FOREIGN KEY (`card_id`) REFERENCES `t_id_card` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8
insert into `t_person` (`id`, `birthday`, `name`, `sex`, `card_id`) values('1','2020-04-13 11:57:03','张三','男','1');
//------------身份证表---------------
CREATE TABLE `t_id_card` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`census_register` varchar(50) DEFAULT NULL,
`id_number` varchar(19) DEFAULT NULL,
`nation` varchar(50) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8
insert into `t_id_card` (`id`, `census_register`, `id_number`, `nation`) values('1','湖南省郴州市xxx县','431022199502367215','汉');
@Entity(name = "t_person")
@Getter
@Setter
@EqualsAndHashCode(exclude = {"idCard"}) //不重写当前类的idCard字段equals()和HashCode()方法,否则会因为循环依赖保存
@ToString(exclude = {"idCard"})//不重写当前类的idCard字段ToString()方法,,否则会因为循环依赖保存
/**
* 人
*/
public class Person {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", nullable = false)
private Long id;//id
@Column(name = "name", nullable = true, length = 20)
private String name;//姓名
@Column(name = "sex", nullable = true, length = 1)
private String sex;//性别
@Column(name = "birthday", nullable = true)
private Timestamp birthday;//出生日期
@OneToOne(cascade = CascadeType.ALL)//Person是关系的维护端,当删除 Person,会级联删除 IDCard
@JoinColumn(name = "card_id", referencedColumnName = "id")//Person表中的card_id字段参考IDCard表中的id字段,会在t_person表多生成一个card_id字段
private IDCard idCard;//身份证
}
身份证类
@Entity(name = "t_id_card")
@Getter
@Setter
@EqualsAndHashCode(exclude = {"person"})
@ToString(exclude = {"person"})
/**
* 身份证
*/
public class IDCard {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", nullable = false)
private Long id;//id
@Column(name = "id_number", nullable = true, length = 19)
private String idNumber;//身份证好
@Column(name = "nation", nullable = true, length = 50)
private String nation;//民族
@Column(name = "census_register", nullable = true, length = 50)
private String censusRegister;//户籍地址
@OneToOne(mappedBy = "idCard", cascade = {CascadeType.MERGE, CascadeType.REFRESH}, optional = false) //mappedBy的意思就是“被映射”,即mappedBy这方不用管关联关系,关联关系交给另一方处理
private Person person;
}
持久层接口
/**
* 用户持久层接口
*/
public interface UsersRepository extends JpaRepository<Users, Integer>,JpaSpecificationExecutor<Users>{
}
/**
* 身份证持久层接口
*/
public interface IDCardRepository extends JpaRepository<IDCard, Integer>, JpaSpecificationExecutor<IDCard> {
}
测试
/**
* 一对一关联关系测试
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class OneToOneTest {
@Autowired
private PersonRepository personRepository;
@Autowired
private IDCardRepository idCardRepository;
/**
* 添加用户同时添加角色
*/
@Test
public void testAdd() {
//创建角色
Person person = new Person();
person.setBirthday(new Timestamp(System.currentTimeMillis()));
person.setName("张三");
person.setSex("男");
IDCard idCard = new IDCard();
idCard.setIdNumber("431022199502367215");
idCard.setCensusRegister("湖南省郴州市xxx县");
idCard.setNation("汉");
person.setIdCard(idCard);
//保存数据
this.personRepository.save(person);
}
/**
* 根据用户ID查询用户,以及身份证信息
*/
@Test
public void testGetPeople() {
Person personParam = new Person();
personParam.setId(1L);
Example<Person> example = Example.of(personParam);
Optional<Person> peopleOptional = this.personRepository.findOne(example);
System.out.println("用户信息:" + peopleOptional.get());
IDCard idCard = peopleOptional.get().getIdCard();
System.out.println("身份证信息:" + idCard);
//用户信息:Person(id=1, name=张三, sex=男, birthday=2020-04-13 11:57:03.0)
//身份证信息:IDCard(id=1, idNumber=431022199502367215, nation=汉, censusRegister=湖南省郴州市xxx县)
}
/**
* 根据身份证id查询身份证信息,以及该身份证对对应的用户
*/
@Test
public void testGetAddress() {
IDCard addressParam = new IDCard();
addressParam.setId(1L);
Example<IDCard> example = Example.of(addressParam);
Optional<IDCard> idCardOptional = this.idCardRepository.findOne(example);
System.out.println("身份证信息:" + idCardOptional.get());
Person person = idCardOptional.get().getPerson();
System.out.println("用户信息:" + person);
//身份证信息:IDCard(id=1, idNumber=431022199502367215, nation=汉, censusRegister=湖南省郴州市xxx县)
//用户信息:Person(id=1, name=张三, sex=男, birthday=2020-04-13 11:57:03.0)
}
}
需求:部门与员工的一对一的关联关系
部门:一端
员工:多端
JPA使用@OneToMany
和@ManyToOne
来标识一对多的双向关联。一端(Department )
使用 @OneToMany ,多端(Employee )
使用 @ManyToOne。
在JPA规范中,一对多的双向关系由多端(Employee )
来维护。就是说多端(Employee )
为关系维护端,负责关系的增删改查。一端(Department )
则为关系被维护端,不能维护关系。
一端(Department)
使用 @OneToMany 注释的mappedBy="xxxxx"
属性表明Department 是关系被维护端。
多端(Employee )
使用 @ManyToOne 和 @JoinColumn 来注释属性dept_id
,@ManyToOne表明Employee是多端,@JoinColumn 设置在Employee
表中的关联字段(外键)。
部门类
@Entity
@Table(name = "tb_dept")
@Setter
@Getter
@EqualsAndHashCode(exclude = {"employees"})//不使用employees字段参与hashCode/equals的重写
@ToString(exclude = {"employees"})//不使用employees字段参与toString的重写
/**
* 部门
*/
public class Department {
@Id
@GenericGenerator(name = "idGenerator", strategy = "uuid")//自定义主键生成策略自动插入uuid
@GeneratedValue(generator = "idGenerator")
private String id;
@Column(name = "dept_name", unique = true, nullable = false, length = 64)
private String deptName;
//@OneToMany(mappedBy = "department", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@OneToMany(targetEntity = Employee.class, cascade = CascadeType.ALL,fetch = FetchType.EAGER) //先保存targetEntity,设置外键后,再保存sourceEntity。
@JoinColumn(name="dept_id",referencedColumnName = "id")
private Set<Employee> employees;
}
关于级联,一定要注意: cascade = CascadeType.ALL 只能写在 One 端,只有One端改变Many端,不准Many端改变One端。
.
比如 文章和评论,文章是One,评论是Many,如果删除一条评论,就把文章删了,那算谁的。所以,在使用的时候要小心。cascade = CascadeType.ALL一定要在 One 端使用。
员工类
@Entity
@Table(name = "tb_emp")
@Setter
@Getter
@EqualsAndHashCode(exclude = {"department"})//不使用department字段参与hashCode/equals的重写
@ToString(exclude = {"department"})//不使用department字段参与toString的重写
/**
* 员工
*/
public class Employee {
@Id
@GenericGenerator(name = "idGenerator", strategy = "uuid")//自定义主键生成策略自动插入uuid
@GeneratedValue(generator = "idGenerator")
private String id;
@Column(name = "emp_name", nullable = false, length = 64)
private String empName;
@Column(name = "emp_job", length = 64)
private String empJob;
@ManyToOne(targetEntity = Department.class, fetch = FetchType.LAZY)
//targetEntity:先保存targetEntity,设置外键后,再保存sourceEntity。
//FetchType.LAZY: 开启懒加载 需要在配置文件配置 spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true
@JoinColumn(name="dept_id")//会自动根据配置在数据表生成dept_id字段
private Department department;
}
持久层接口
public interface DepartmentRepository extends JpaRepository<Department, String>, JpaSpecificationExecutor<Department> {
}
public interface EmployeeRepository extends JpaRepository<Employee, String>, JpaSpecificationExecutor<Employee> {
}
测试
/**
* 一对多/多对一关联关系测试
*
* @author Administrator
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class OneToManyTest {
@Autowired
private EmployeeRepository employeeRepository;
@Autowired
private DepartmentRepository departmentRepository;
/**
* 添加部门
*/
@Test
public void testDepartmentAdd() {
Department department = new Department();
department.setDeptName("市场部");
departmentRepository.save(department);
}
/**
* 添加员工并设置外键
*/
@Test
public void testEmployeeAdd() {
Employee employee = new Employee();
employee.setEmpName("王九");
employee.setEmpJob("拓展市场");
Department department = new Department();
department.setId("402880e57191dd87017191dd960f0000");
employee.setDepartment(department);
employeeRepository.save(employee);
}
/**
* 查询市场部的所有员工
*/
@Test
public void testFindDepartmentById() {
Optional<Department> departmentOptional = departmentRepository.findById("402880e57191dd87017191dd960f0000");
System.out.println("部门:" + departmentOptional.get());
System.out.println("部门员工:" + departmentOptional.get().getEmployees());
}
/**
* 查询员工所属部门
*/
@Test
public void testFindEmployeeById() {
Optional<Employee> employeeOptional = employeeRepository.findById("402880e57191df20017191df2c8d0000");
System.out.println("员工:" + employeeOptional.get());
System.out.println("所属部门:" + employeeOptional.get().getDepartment());//懒加载
}
}
这里以用户-角色表关系为例,一个用户可以有多个角色,一个角色也可以属于多个用户,所以他们的关系是多对多关系
用户类
@Entity
@Table(name = "tb_user")
@Setter
@Getter
@EqualsAndHashCode(exclude = {"roleList"})//不使用roleList字段参与hashCode/equals的重写
@ToString(exclude = {"roleList"})//不使用roleList字段参与toString的重写
public class User {
@Id
@GenericGenerator(name = "idGenerator", strategy = "uuid")//自动生成UUID
@GeneratedValue(generator = "idGenerator")
private String id;
@Column(name = "username", unique = true, nullable = false, length = 64)
private String username;
@Column(name = "password", nullable = false, length = 64)
private String password;
@Column(name = "email", unique = true, length = 64)
private String email;
// FetchType.LAZY 懒加载方式获取数据
@ManyToMany
@JoinTable(name = "tb_user_role", joinColumns = {@JoinColumn(name = "user_id")},
inverseJoinColumns = {@JoinColumn(name = "role_id")})
//mappedBy:绝对不能再存于JoinTable和JoinColumn注解时使用
//joinColumns:当前类主键 inverseJoinColumns:关联对象主键
private List<Role> roleList;
//1、关系维护端,负责多对多关系的绑定和解除
//2、@JoinTable注解的name属性指定关联表的名字,joinColumns指定外键的名字,关联到关系维护端(User),inverseJoinColumns指定外键的名字,要关联的关系被维护端(Role)
//3、其实可以不使用@JoinTable注解,默认生成的关联表名称为主表表名+下划线+从表表名,
//4.主表就是关系维护端对应的表,从表就是关系被维护端对应的表
}
角色类
@Entity
@Table(name = "tb_role")
@Setter
@Getter
@EqualsAndHashCode(exclude = {"userList"})//不使用userList字段参与hashCode/equals的重写
@ToString(exclude = {"userList"})//不使用userList字段参与toString的重写
public class Role {
@Id
@GenericGenerator(name = "idGenerator", strategy = "uuid")
@GeneratedValue(generator = "idGenerator")
private String id;
@Column(name = "role_name", unique = true, nullable = false, length = 64)
private String roleName;
// FetchType.LAZY 懒加载方式获取数据
@ManyToMany(cascade = CascadeType.ALL)
@JoinTable(name = "tb_user_role", joinColumns = {@JoinColumn(name = "role_id", referencedColumnName = "id")},
inverseJoinColumns = {@JoinColumn(name = "user_id")})
//mappedBy:绝对不能再存于JoinTable和JoinColumn注解时使用
//joinColumns:当前类主键 inverseJoinColumns:关联对象主键
private List<User> userList;
//1、关系维护端,负责多对多关系的绑定和解除
//2、@JoinTable注解的name属性指定关联表的名字,joinColumns指定外键的名字,关联到关系维护端(User),inverseJoinColumns指定外键的名字,要关联的关系被维护端(Role)
//3、其实可以不使用@JoinTable注解,默认生成的关联表名称为主表表名+下划线+从表表名,
//4.主表就是关系维护端对应的表,从表就是关系被维护端对应的表
}
持久层接口
public interface UserRepository extends JpaRepository<User, String>, JpaSpecificationExecutor<User> {
}
public interface RoleRepository extends JpaRepository<Role, String>, JpaSpecificationExecutor<Role> {
}
测试
/**
* 多对多关联关系测试
*
* @author Administrator
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class ManyToManyTest {
@Autowired
private UserRepository userRepository;
@Autowired
private RoleRepository roleRepository;
/**
* 添加角色并生成外键
*/
@Test
public void testRoleAdd() {
Role role = new Role();
role.setRoleName("普通用户");
roleRepository.save(role);
}
/**
* 新增用户张三并绑定所有角色
*/
@Test
public void testUserRoleAdd() {
User user = new User();
user.setUsername("zhangshan");
user.setPassword("123456");
user.setEmail("[email protected]");
List<Role> roleList = roleRepository.findAll();
user.setRoleList(roleList);
userRepository.save(user);
}
/**
* 新新增用户李四并绑定 超级管理员/企业管理员角色
*/
@Test
public void testUserRoleAdd2() {
List<Role> roleList = new ArrayList<>();
Role role1 = new Role();
role1.setId("402880e571922ae80171922afe7e0000");//超级管理员
Role role2 = new Role();
role2.setId("402880e571922ce20171922ceec60000");//企业管理员
roleList.add(role1);
roleList.add(role2);
User user = new User();
user.setUsername("lisi");
user.setPassword("123456222");
user.setEmail("[email protected]");
user.setRoleList(roleList);
userRepository.save(user);
}
/**
* 修改增用户李四并绑定 超级管理员/企业管理员角色
*/
@Test
public void testUserUpdate() {
List<Role> roleList = new ArrayList<>();
Role role1 = new Role();
role1.setId("402880e571922ae80171922afe7e0000");//超级管理员
Role role2 = new Role();
role2.setId("402880e571922ce20171922ceec60000");//企业管理员
roleList.add(role1);
roleList.add(role2);
User user = new User();
user.setId("402880e571929b260171929b33840000");
user.setUsername("lisi-123123123123");
user.setPassword("123456222");
user.setEmail("[email protected]");
user.setRoleList(roleList);
userRepository.save(user);
}
//-----------------------------------------------
/**
* 新增角色运营管理员 并绑定到所有用户
*/
@Test
public void testRoleUserAdd() {
Role role = new Role();
role.setRoleName("运营管理员");
List<User> userList = userRepository.findAll();
role.setUserList(userList);
roleRepository.save(role);
}
/**
* 新增用户 小花/小明 并绑定 普通用户角色
*/
@Test
public void testRoleUserAdd2() {
List<User> userList = new ArrayList<>();
User user1 = new User();
user1.setUsername("xiaohua");
user1.setPassword("123456222");
user1.setEmail("[email protected]");
userList.add(user1);
User user2 = new User();
user2.setUsername("xiaoming");
user2.setPassword("123456222");
user2.setEmail("[email protected]");
userList.add(user2);
Role role = new Role();
role.setId("402880e571922d480171922d53db0000");//普通用户
role.setRoleName("普通用户");
role.setUserList(userList);
roleRepository.save(role);
}
/**
* 查询指定用户及其所属角色
*/
@Test
public void testFindUserById() {
Optional<User> userOptional = userRepository.findById("402880e5719296d301719296e0f90000");
System.out.println("用户:" + userOptional.get());
System.out.println("所属用户角色:" + userOptional.get().getRoleList());
}
/**
* 查询指定角色及其所属用户
*/
@Test
public void testFindRoleById() {
Optional<Role> roleOptional = roleRepository.findById("402880e571922d480171922d53db0000");
System.out.println("角色:" + roleOptional.get());
System.out.println("所属角色用户:" + roleOptional.get().getUserList());
}
}
不出意外会报Hibernate懒加载异常,无法初始化代理类,No Session:
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.example.springbootjpa.entity.User.roles, could not initialize proxy - no Session
原因: Spring Boot整合JPA后Hibernate的Session就交付给Spring去管理。每次数据库操作后,会关闭Session,当我们想要用懒加载方式去获得数据
的时候,原来的Session已经关闭,不能获取数据,所以会抛出这样的异常。
解决方法:
在application.peoperties
中做如下配置:
spring.jpa.open-in-view=true
spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true
使用lombok框架时可能会操作循环依赖问题:慎用
@Data
注解,使用@Getter、@Setter注解,需要时自己重写toString()、equals()
以及hashCode()
方法
使用Hibernate、JPA、Lombok遇到的有趣问题cmazxiaoma
参考自官方文档5.9Auditing
@CreatedBy,@LastModifiedBy,@CreatedDate,@LastModifiedDate
4个注解来记录表中记录的创建及修改信息。1.在SpringBoot启动类上添加@EnableJpaAuditing
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
@SpringBootApplication
@EnableJpaAuditing
public class SpringBootJpaApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootJpaApplication.class, args);
}
}
2.在实体类上使用以上注解
package com.example.springbootjpa.entity;
import lombok.Data;
import org.hibernate.annotations.GenericGenerator;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import javax.persistence.*;
import java.util.Date;
import java.util.Set;
@Entity
@EntityListeners(AuditingEntityListener.class)
@Table(name = "tb_user")
@Data
public class User {
@Id
@GenericGenerator(name = "idGenerator", strategy = "uuid")
@GeneratedValue(generator = "idGenerator")
private String id;
@Column(name = "username", unique = true, nullable = false, length = 64)
private String username;
@Column(name = "password", nullable = false, length = 64)
private String password;
@Column(name = "email", unique = true, length = 64)
private String email;
@ManyToMany(targetEntity = Role.class, cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@JoinTable(name = "tb_user_role", joinColumns = {@JoinColumn(name = "user_id", referencedColumnName = "id")},
inverseJoinColumns = {@JoinColumn(name = "role_id", referencedColumnName = "id")})
private Set<Role> roles;
@CreatedDate
@Column(name = "created_date", updatable = false)
private Date createdDate;
@CreatedBy
@Column(name = "created_by", updatable = false, length = 64)
private String createdBy;
@LastModifiedDate
@Column(name = "updated_date")
private Date updatedDate;
@LastModifiedBy
@Column(name = "updated_by", length = 64)
private String updatedBy;
}
实体类上还添加了
@EntityListeners(AuditingEntityListener.class)
,而AuditingEntityListener
是由Spring Data Jpa提供的
3.实现 AuditorAware 接口,泛型T是返回的限定类型,并注册到Spring管理的容器中
package com.example.springbootjpa.auditing;
import org.springframework.data.domain.AuditorAware;
import org.springframework.stereotype.Component;
import java.util.Optional;
//测试处理审计字段方式
@Component
public class AuditorAwareImpl implements AuditorAware<String> {
@Override
public Optional<String> getCurrentAuditor() {
return Optional.of("admin");
}
}
//真实处理审计字段方式
/*class SpringSecurityAuditorAware implements AuditorAware {
public Optional getCurrentAuditor() {
return Optional.ofNullable(SecurityContextHolder.getContext())
.map(SecurityContext::getAuthentication)
.filter(Authentication::isAuthenticated)
.map(Authentication::getPrincipal)
.map(User.class::cast);
}
}*/
这里简单的返回了一个"admin"字符串来代表当前用户名
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;
@MappedSuperclass
@Data
public class BaseEntity implements Serializable{
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@column(name="created_by")
private String createdBy;
@column(name="created_at")
private Integer createdAt;
@column(name="updated_by")
private String updatedBy;
@column(name="updated_at")
private Integer updatedAt;
@column(name="is_deleted")
private Integer isDeleted;
}
子类
import javax.persistence.Entity;
import javax.persistence.Table;
@Entity
@Table(name = "yyh_user")
public class User extends BaseEntity{
}
使用方式:
@MappedSuperclass注解使用在一个实体类父类上,来标识这个父类。
@MappedSuperclass标识的类表示其不能映射到数据库表,因为其不是一个完整的实体类,但它所拥有的属性能够映射到其子类所在的表中。
@MappedSuperclass标识的类不能再有@Entity和@Table注解。
写完之后才感觉有点写多了,这篇文章我单纯是为了学习SpringDataJPA而写下的笔记,大多了地方都使用了伪代码方式实现,有点繁杂了,如果有什么不懂的可以在下方进行留言,我会继续为大家补充