spring data JPA 养吾剑总结
jpa是sun的orm规范,操作api全都是抽象类和接口,hibernate等框架对这套规范进行了实现。今天学习的就是基于hibernate的jpa。现如今hibernate原生api基本已经被jpa取代,所以不再讲解原生hibernate。
spring又对jpa规范的代码进行了封装,相当于hibernateTemplate和hibernate原生api的关系,这就是spring data JPA 。市场上原生jpa和springdatajpa都有运用,所以先介绍jpa,再介绍springdatajpa
基本配置
maven配置
<dependencies>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>5.4.10.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-c3p0</artifactId>
<version>5.4.10.Final</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.12</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.13</version>
</dependency>
</dependencies>
配置文件resources/META-INF/persistence.xml
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="2.0">
<!--unit为持久化单元节点:
name:单元名称
type:事务管理方式
JTA:分布式事务管理
RESOURCE_LOCAL 单表本地事务管理
-->
<persistence-unit name="myJpa" transaction-type="RESOURCE_LOCAL">
<!--jpa的实现方式-->
<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
<!--数据源信息-->
<properties>
<property name="javax.persistence.jdbc.driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/test"/>
<property name="javax.persistence.jdbc.user" value="root"/>
<property name="javax.persistence.jdbc.password" value="JIANGkui1"/>
<!--可选配置,配置jpa实现方的配置信息,也即hibernate的可选配置-->
<property name="hibernate.show_sql" value="true"/>
<property name="hibernate.format_sql" value="true"/>
<property name="hibernate.hbm2ddl.auto" value="validate"/>
</properties>
</persistence-unit>
</persistence>
实体类映射
@Entity
public class Account {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
// private Integer userid;
private String name;
private Float money;
@ManyToOne(fetch = FetchType.LAZY,cascade = {CascadeType.PERSIST,CascadeType.MERGE})
@JoinColumn(name = "userid")
@BatchSize(size = 10)
private User user;
}
@Entity 相当于class标签,指定当前类是一个实体类,name属性为实体名称。默认为类的非限定性类名,用于hql查询。
@Table name属性指定映射的表名。默认与实体名称相同。
@Id用在主键id属性上,或者其get方法上,表示当前属性将对应数据库中的主键。
@GeneratedValue 将指定主键值的来源,其属性strategy用于指定主键的生成策略。其值为系统定义好的四种策略之一。默认为AUTO(native)。尽量写@GeneratedValue(strategy = GenerationType.IDENTITY)不会报错。
@Column name/nullable/length/insertable/updateable
precision和scale表示精度,当字段类型为double时,precision表示数值的总长度,scale表示小数点所占的位数;
@Basic:表示该字段将映射到DB中,是属性的默认注解;
@Transient:表示该字段不映射到DB中,该注解无属性;
@Temporal(TemporalType.DATE) 指定输出日期的格式
表关联:
@OneToMany表明是一对多关联关系;属性如下:
targetEntity:指明该属性所关联的类。
cascade:指定级联类型。其为数组,使用多种级联,则可使用{}赋值。其值为Cascade常量。
mappedBy:string类型,写对方属性名,等价于inverse=true,放弃外键维护。使用该属性后将不能够再使用@JoinColumn注解。
fetch = FetchType.EAGER
@JoinColumn指明该属性所关联的外键;设置该属性后默认拥有外键维护权
@ManyToMany注解 其会自动生成一个中间表,表名为两个关联对象的映射表名的联合:表1_表2。只不过,谁在维护关联关系,谁的表名在前。字段名与谁在维护关联关系相关。谁在维护关联关系,谁的表名将出现在第一个字段名中,而该类的关联属性名将出现在第二个字段名中。字段名分别为表名_id与关联属性名_id。
@JoinTable 指定中间表的一些设置
name:中间表名称
joinColumns,本实体类在中间表的外键列名字,@JoinColumn(name = “studentId”)这样写。
inverseJoinColumns 对方表在中间表外键列的名字
一对多双向关联范例:有实体外键,即多一方定义JoinColumn,另一方mappedBy即可。
@ManyToOne(fetch = FetchType.LAZY,cascade ={CascadeType.PERSIST,CascadeType.MERGE})
@JoinColumn(name = "userid")
private User user;
@OneToMany(fetch = FetchType.LAZY,mappedBy = "user",cascade = CascadeType.ALL)
private Set<Account> accounts =new HashSet<Account>();
多对多双向关联范例:
在逻辑上的主动方定义JoinTable即可,这样,被动方是不能管理外键的。
@ManyToMany(cascade = {CascadeType.PERSIST,CascadeType.MERGE})
@JoinTable(name="user_role",joinColumns = @JoinColumn(name = "uid"),inverseJoinColumns = @JoinColumn(name = "rid"))
private Set<Role> roles=new HashSet<Role>();
@ManyToMany(mappedBy = "roles" , cascade = {CascadeType.PERSIST, CascadeType.MERGE})
private Set<User> users=new HashSet<User>();
使用api
public class Jpatest {
static EntityManager manager;
static EntityTransaction transaction;
static EntityManagerFactory factory;
@BeforeClass
public static void init(){
// Configuration configuration=new Configuration().configure();
// managerFactory managerFactory = configuration.buildmanagerFactory();
// manager = managerFactory.getCurrentmanager();
// transaction = manager.beginTransaction();
factory = Persistence.createEntityManagerFactory("myJpa");
manager = factory.createEntityManager();
transaction = manager.getTransaction();
transaction.begin();
}
@AfterClass
public static void close(){
// transaction.commit();
manager.close();
transaction.commit();
manager.close();
factory.close();
}
@Test
public void testsave(){
Account account = new Account();
account.setMoney(2001f);
account.setName("mybatis-annotasion");
User user = new User();
user.setUserName("天皇号");
user.getAccounts().add(account);
account.setUser(user);
// manager.save(user);
manager.persist(account);
}
}
EntityManager对象基本删增改查:
presist、merge、remove、find/getRefrence
和hibernate:save/update/delete/get/find基本一一对应。
概念介绍
【三态】
1)瞬时态(transient)未保存
瞬时态也称为临时态或者自由态,瞬时态的对象是由 new 关键字开辟内存空间的对象,不存在持久化标识 OID(相当于主键值),且未与任何的 manager 实例相关联,在数据库中也没有记录,失去引用后将被 JVM 回收。瞬时对象在内存孤立存在,它是携带信息的载体,不和数据库的数据有任何关联关系。
2)持久态(persistent)已保存
持久态的对象存在一个持久化标识 OID,当对象加入到 manager 缓存中时,就与 manager 实例相关联。它在数据库中存在与之对应的记录,每条记录只对应唯一的持久化对象。需要注意的是,持久态对象是在事务还未提交前变成持久态的。
3)脱管态(detached)已断线
脱管态也称离线态或者游离态,当持久化对象与 manager 断开时就变成了脱管态,但是脱管态依然存在持久化标识 OID,只是失去了与当前 manager 的关联。需要注意的是,脱管态对象发生改变时 Hibernate 是不能检测到的。
注意:持久化态的对象在commit时,会和缓存做比较,当内容发生变化时,会自动向数据库发送update的sql语句。
【一级缓存】
1、保存时默认将数据库返回的已持久化对象加入缓存(不会等提交,而是马上发sql)。
2、查询时先查缓存,缓存没有,才会查数据库并加入缓存。
3、manager关闭时,缓存清空,clear()方法调用后,也将清空缓存,此后提交也不会再自动发更新语句。
刷出缓存(将缓存和快照的改变发送到数据库):
1、 commit()方法执行时,会进行保存内容和缓存的对比,智能发送update的sql语句。
2、select时,如果缓存中的对象已经发生变化,会更新缓存。
3、调用manager.flush方法,会默认刷新缓存,强制将缓存与快照的变化更新的数据库。
3、调用.refresh方法,会重新查询数据库,用数据库值覆盖缓存。
【事务管理】
thread
开启manager线程绑定,然后所有获取manager 的地方都用:
manager = managerFactory.getCurrentmanager();
保证一个线程获取的都是同一个manager,即可完成事务管理。
查询select
【JPQL查询】
1、别名查询:
from Account a;select a from Account;
2、排序查询:
from User order by id,userName desc ;默认升序
3、条件查询:
Query query = manager.createQuery("from Account where name like ?1");
query.setParameter(1,"杨%");位置匹配
//Query query = manager.createQuery("from Account where name like :name");
//query.setParameter("name","杨%");名字匹配
4、投影查询:
Query query = manager.createQuery("select u.userName,u.sex from User u");
List<Object[]> list = query.list();
第二种,个别封装:
Query query = manager.createQuery("select new User (u.userName,u.birthday,u.sex) from User u");
List<User> list = query.list();
5、分页查询
query.setFetchSize(0);
query.setMaxResults(5);
返回从1-5。公式为pagenum-1)*pagesize,pagesize
6、聚合函数
Long re =(Long) manager.createQuery("select count() from User ").uniqueResult();
List list = manager.createQuery("select count(distinct userName),address from User group by address").list();
7、多表连接
//内连接:
List<Object[]> list = manager.createQuery("from Account a inner join a.user").list();//注意,必须使用别名查询。
//迫切内连接,封装成对象,会有封装重复问题
List<Account> list = manager.createQuery("from Account a inner join fetch a.user").list();
select distinct a from Account a inner join fetch a.user //这样就没有封装重复问题了
【离线条件查询】
DetachedCriteria detachedCriteria = DetachedCriteria.forClass(User.class);
detachedCriteria.add(Restrictions.like("userName","杨%"));
Criteria criteria = detachedCriteria.getExecutableCriteria(manager);
List<User> list = criteria.list();
【原生sql查询】
NativeQuery sqlQuery = manager.createSQLQuery("select * from user");
sqlQuery.addEntity(User.class);
List<User> list = sqlQuery.list();
【qbc查询】
通过criteriaQuery.where/from/orderby/gropyby来指定具体的语句
通过CriteriaBuilder.like/or/and来指定具体的条件。
// 获取工厂对象
CriteriaBuilder criteriaBuilder = manager.getCriteriaBuilder();
// 获取针对某个表的条件对象,相当于指定返回值的封装类型
CriteriaQuery<Account> criteriaQuery = criteriaBuilder.createQuery(Account.class);
// 指定根条件,相当于写from语句
Root<Account> root = criteriaQuery.from(Account.class);
// 指定查询条件,相当于写where语句,多条件用工厂的and或or链接
Predicate equal1 = criteriaBuilder.like(root.<String>get("name"),"杨%");
Predicate lt1 = criteriaBuilder.gt(root.<Number>get("money"), 1000);
criteriaQuery.where(criteriaBuilder.and(equal1,lt1));
// 指定排序
criteriaQuery.orderBy(criteriaBuilder.desc(root.get("id")), criteriaBuilder.asc(root.get("userid")));
// 使用manager执行查询语句,与hql一致了
Query<Account> query = manager.createQuery(criteriaQuery);
query.setFetchSize(0);
query.setMaxResults(5);
List<Account> accounts = query.list();
补充,旧版写法:
Configuration config = new Configuration().configure();
// 2.通过manager获得Criteria对象
Criteria criteria = manager.createCriteria(User.class);
// 3.使用Restrictions的eq方法设定查询条件为name="zhangsan"
// 4.向Criteria对象中添加查询条件
criteria.add(Restrictions.eq("name", "zhangsan"));
// 5.执行Criterita的list()方法获得结果
List<User> list = criteria.list();
springDataJpa
springDataJpa的好处是可以写接口的方式快速实现操作Dao,遮蔽entityManage的操作,这样操作体验就类似有mybatis-plus加持的mybatis了。
基础配置
maven依赖
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<!--数据库-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.13</version>
</dependency>
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
<!--SPRING-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.7</version>
</dependency>
<!--hibernate-->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>5.4.10.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>5.4.10.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>5.4.1.Final</version>
</dependency>
<!--springdata jpa-->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<version>2.1.10.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.el</groupId>
<artifactId>javax.el-api</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>org.glassfish.web</groupId>
<artifactId>javax.el</artifactId>
<version>2.2.6</version>
</dependency>
<!--log-->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.5</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.5</version>
</dependency>
</dependencies>
使用spirng配置dao的所有配置,包括jpa的配置
<?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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
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"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.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
http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
">
<context:component-scan base-package="htyy"/>
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
<tx:annotation-driven></tx:annotation-driven>
<!--创建工厂对象,交给spring管理-->
<bean id="entityManagerFactoryBean" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource"/>
<!--配置实体类所在包,用于扫描entity等映射注解-->
<property name="packagesToScan" value="htyy.domain"/>
<!--jpa实现方式-->
<property name="persistenceProvider">
<bean class="org.hibernate.jpa.HibernatePersistenceProvider"/>
</property>
<!--配置hibernate的一些可选配置-->
<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>
<!--jpa的方言,即高级特性-->
<property name="jpaDialect">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaDialect"/>
</property>
<!--配置hibernate自己的属性-->
<property name="jpaProperties">
<props>
<prop key="hibernate.hbm2ddl.auto">validate</prop>
</props>
</property>
</bean>
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.cj.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test"></property>
<property name="user" value="root"></property>
<property name="password" value="JIANGkui1"></property>
</bean>
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager" >
<property name="entityManagerFactory" ref="entityManagerFactoryBean"/>
</bean>
<!--spring整合datajpa,也即自动扫描dao接口,创建实现类-->
<jpa:repositories base-package="htyy.dao" transaction-manager-ref="transactionManager"
entity-manager-factory-ref="entityManagerFactoryBean"/>
</beans>
编写dao接口,继承jpa的含有默认方法的接口类
/**
* JpaRepository<Account,Integer> 封装了基本crud操作,泛型为要操作的实体对象和该实体对象的主键类型
* JpaSpecificationExecutor<Account> 封装了复杂查询(分页等)
*/
@Repository
public interface IAccountDao extends JpaRepository<Account,Integer>, JpaSpecificationExecutor<Account> {
}
此时自己编写的iaccountdao已经具有所有方法,如:
accountDao.findById(3); 返回一个代理对象,可以通过get或者orelse得到account对象,建议后者。
accountDao.findById(24).orElse(null);标准的orelse方法得到实体类
accountDao.getOne(1); lazy查询的方法,延迟加载的方式得到代理对象。
accountDao.deleteById(2);accountDao.delete(account);删除,可以根据id,或者删除查询到的实体。
accountDao.findAll();查询所有。
accountDao.save(account) 保存或更新都是调用的这个方法,有id更新,无id保存。
accountDao.count() 返回一个long类型的总数量。
accountDao.existsById(4) 返回一个boolean,判断是否存在该id的值。
select查询
【hql查询(jpaql查询)】
使用@Query注解在接口方法名上写hql语句即可:
@Query("from Account where name like ?1")
public List<Account> findByname(String name);
也可以使用@Modifying+@Query表示要写update语句
@Query("update Account set name = ?1")
@Modifying
public List<Account> findByname(String name);
注意,query注解有一个nativeQuery参数,默认为false,true代表使用sql查询,查询的结果不能自动封装,只能使用list<object[]>进行接收。
参数解析的方式有如下几种:
1、按位置获取?1 ?2
2、按名称获取:name :money,此时需要使用@param注解在方法中进行名称匹配:
@Param("name") String name,@Param("money") String money
3、类似mybatis,使用#{#表达式}
@Query("from Account where name like :#{#account.name} and money >= :#{#account.money}")
public List<Account> findByExample(@Param("account") Account account);
【方法名称规则查询】
如果按照springdatajpa固定的方法规则,那么会自动給该方法进行代理实现。
1、findBy+规范属性名(属性名称首字母大写),即可完成一个from entity where fieldname=?的查询。
public List findByName(String name);
2、findBy+规范属性名+查询方式(Like/isnull),可以指定查询方式是=,like,isnull。
3、多参数情况下,findBY+(属性名查询)+And/Or+(属性名查询)即可,排序等也可以写在后面:
findByNameLikeAndMoneyAfterOrderByMoney
【复杂条件查询】
Optional<T> findOne(@Nullable Specification<T> var1); //查询一个
List<T> findAll(@Nullable Specification<T> var1);//查询多个
Page<T> findAll(@Nullable Specification<T> var1, Pageable var2);//带分页,返回pageinfo对象
List<T> findAll(@Nullable Specification<T> var1, Sort var2);//可排序
long count(@Nullable Specification<T> var1);//返回count的数量
Specification是一个接口,使用时应该使用内部类或lambd表达式自定义自己的实现类。
List<Account> all = accountDao.findAll((Specification<Account>) (root, query, cb) ->
cb.and(cb.like(root.get("name"), "杨%"),cb.gt(root.get("money"),3000f)));
Predicate toPredicate(Root var1, CriteriaQuery<?> var2, CriteriaBuilder var3);实现该方法。
可以发现,实际上该接口就是对cb、cq、root这三个qbc查询的封装,编写好规则后,使用时会默认传入。最终,只需要返回cb对象合成的一个predicate条件即可。
//sort是一个排序类:
Sort orders = new Sort(Sort.Direction.DESC, "money", "id");
Sort orders1 = orders.and(new Sort(Sort.Direction.ASC, "name"));
//pageable是一个接口,我们需要使用它的实现类pagerequest:
PageRequest pageRequest = PageRequest.of(page:2, size:1, sort:orders1);
//三个参数分别是当前页(从0开始),每页数量,排序条件。
Page可用方法、属性如下:
getTotalElements() 当前总项目数
getTotalpages 当前总页数
getSize 得到当前设置的每页容量
getnumber 得到当前页,从0开始
getContent 得到内置的list