JPA是Java Persistence API的简称,中文名Java持久层API。是Java EE5.0平台中Sun为了统一持久层ORM框架而制定的一套标准,注意是一套标准,而不是具体实现,不可能单独存在,需要有其实现产品。Sun挺擅长制定标准的,例如JDBC就是为了统一连接数据库而制定的标准,而我们使用的数据库驱动就是其实现产品。JPA的实现产品有HIbernate,TopLink,OpenJPA等等。值得说一下的是Hibernate的作者直接参与了JPA的制定,所以JPA中的一些东西可以与Hibernate相对比。
JPA特点:
JPA可以使用xml和注解映射源数据,JPA推荐使用注解来开发。
它有JPQL语言也是面向对象的查询语言,和hql比较类似。
我们使用Hibernate作为JPA的实现产品,需要导入的jar包有:
其实也就是Hibernate的required包下的jar,重点在于有Hibernate-jpa-api这个jar的存在
注意
:不要忘了我们的数据库驱动jar
Persistence.xml文件的编写
JPA规范要求在内路径下META-INF目录下放置persistence.xml文件,文件名称是固定的。这个文件在于spring整合之后就可以取消了。
一个简单persistence.xml文件配置:
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0"
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">
<persistence-unit name="jpa-1" transaction-type="RESOURCE_LOCAL">
配置使用什么 ORM 产品来作为 JPA 的实现
1. 实际上配置的是 javax.persistence.spi.PersistenceProvider 接口的实现类
2. 若 JPA 项目中只有一个 JPA 的实现产品, 则也可以不配置该节点.
<!-- <provider>org.hibernate.ejb.HibernatePersistence</provider> -->
<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
<!-- 添加持久化类 (推荐配置)-->
<class>cn.lynu.model.User</class>
<properties>
<!-- 连接数据库的基本信息 -->
<property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
<property name="javax.persistence.jdbc.url" value="jdbc:mysql:///jpa"/>
<property name="javax.persistence.jdbc.user" value="root"/>
<property name="javax.persistence.jdbc.password" value="root"/>
<!-- 配置 JPA 实现产品的基本属性. 配置 hibernate 的基本属性 -->
<property name="hibernate.format_sql" value="true"/>
<property name="hibernate.show_sql" value="true"/>
<property name="hibernate.hbm2ddl.auto" value="update"/>
</properties>
</persistence-unit>
</persistence>
name:用于定义持久单元名称,必须。
transaction-type:指定JPA的事务处理策略,RESOURCE_LOCAL 是默认值,数据库级别的事务,只支持一种数据库,不支持分布式事务。如果要支持分布式事务,要使用JTA策略:transaction-type:“JTA”
开始操作实体类
我们可以使用注解来映射源数据了,而不用再编写如Hibernate中的*.hbm.xml文件了,Hibernate中其实就可以使用JPA的注解来映射数据,学习了JPA之后,通过使用JPA来增加Dao层的通用性。
@Entity
标注用于实体类声明语句之前,指出该Java 类为实体类,将映射到指定的数据库表。如声明一个实体类 Customer,它将映射到数据库中的 customer 表上。
@Table
当实体类与其映射的数据库表名不同名时需要使用 @Table 标注说明,该标注与 @Entity 标注并列使用。
常用选项是 name,用于指明数据库的表名。
标注用于声明一个实体类的属性映射为数据库的主键列,可以使用在属性上,也可以使用在getter方法上
@GeneratedValue
用于标注主键的生成策略,通过 strategy 属性指定。默认情况下,JPA 自动选择一个最适合底层数据库的主键生成策略:
SqlServer 对应 identity,MySQL 对应 auto increment
在 javax.persistence.GenerationType 中定义了以下几种可供选择的策略(可以看到JPA没有Hibernate的主键生成策略多,因为其只是做一个抽象):
IDENTITY:采用数据库 ID自增长的方式来自增主键字段,Oracle 不支持这种方式;
AUTO: JPA自动选择合适的策略,是默认选项;
SEQUENCE:通过序列产生主键,通过 @SequenceGenerator 注解指定序列名,MySql 不支持这种方式;
TABLE:通过表产生主键,框架借由表模拟序列产生主键,使用该策略可以使应用更易于数据库移植。
在我的Mysql使用@GeneratedValue为默认值时,会识别为序列的方式,所以我都是自己指定为IDENTITY方式。
Sequence策略
在Oracle中没有主键自动增长,所以都是使用序列替代这一功能的,GeneratedValue如何通过序列产生主键呢?
首先我们的Oracle数据库中需要创建一个序列
将GeneratedValue的strategy设置为GenerationType.SEQUENCE
还需要使用一个@SequenceGenerator 注解来配置一些序列的信息
@GeneratedValue(generator="seq",strategy=GenerationType.SEQUENCE)
@SequenceGenerator(name="seq",allocationSize=1,sequenceName="master_seq")
@GeneratedValue的generator的值和@SequenceGenerator的name值需要保持一致;allocationSize 表明每次增长1,其实这个属性在数据库中创建序列的时候可以指定步长,所以可以不写;sequenceName需要指明在数据库中我们创建的序列名。
TABLE策略
将当前主键的值单独保存到一个数据库的表中(存主键的表),主键的值每次都是从指定的表中查询来获得 这种方法生成主键的策略可以适用于任何数据库,不必担心不同数据库不兼容造成的问题。
就不是只使用@GeneratedValue注解了,而是使用@TableGenerator和@GeneratedValue配合:
name :表示该主键生成策略的名称,它被引用在@GeneratedValue中设置的generator 值中
table :表示表生成策略所持久化的表名
pkColumnName :表示在持久化表中,该主键生成策略所对应键值的名称(存主键的表中表示id的字段名)
pkColumnValue :表示在持久化表中,该生成策略所对应的主键(也就是该实体类对应的表的主键字段名)
valueColumnName :表示在持久化表中,该主键当前所生成的值,它的值将会随着每次创建累加(存主键的表中表示值的字段名)
所以生成的存主键的表结构为:
pk_name就是在pkColumnName 中设置的,对应需要使用table方式生成主键的表的id
pk_value就是在valueColumnName 中设置的,其值并非是在设置主键初始值
@Column
当实体的属性与其映射的数据库表的列不同名时需要使用@Column 标注说明,该属性通常置于实体的属性声明语句之前,还可与 @Id 标注一起使用。
@Column 标注的常用属性是 name,用于设置映射数据库表的列名。此外,该标注还包含其它多个属性,如:unique 、nullable、length 等。
@Transient
表示该属性并非一个到数据库表的字段的映射,ORM框架将忽略该属性. 如果一个属性并非数据库表的字段映射,就务必将其标示为@Transient,否则,如果我们使用了自动建表,就会将不需要的属性映射为表的字段。
@Temporal
在核心的 Java API 中并没有定义 Date 类型的精度(temporal precision). 而在数据库中,表示 Date 类型的数据有 DATE, TIME, 和 TIMESTAMP 三种精度(即单纯的日期,时间,或者两者兼备). 在进行属性映射时可使用@Temporal注解来调整精度。
默认将时间类型映射为时间戳类型,也就是有日期有时间的,如果我们只需要日期,如生日字段,就需要使用Date类型,只需要时间,就使用Time类型
JPA API
创建EntityManagerFactory和EntityManager
JPA也是需要先创建一个EntityManagerFactory(类似于Hibernate中的SessionFactory),通过这个工厂再来生成EntityManager(类似于Hibernate的Session)。
EntityManagerFactory是通过Persistence类的静态方法 createEntityManagerFactory生成,方法的参数指定JPA的持久单元名称,也就是我们在persistence.xml文件中写的name名。
EntityManager再通过EntityManagerFactory的createEntityManager方法创建。
开启事务使用的是:entityManager.getTransaction();得到一个EntityTransaction 对象,再通过这个对象的begin方法就可以开启事务了
private EntityManagerFactory entityManagerFactory;
private EntityManager entityManager;
private EntityTransaction transaction;
@Before
public void init() {
entityManagerFactory=Persistence.createEntityManagerFactory("jpa-1");
entityManager=entityManagerFactory.createEntityManager();
transaction=entityManager.getTransaction();
//开启事务
transaction.begin();
我们再来写一个关闭的方法,规范一下代码:
@After
public void destroy() {
//提交事务
transaction.commit();
entityManager.close();
entityManagerFactory.close();
注意:我们使用的JAP的类和注解都是使用的javax.persistence包下的,不要导错了
EntityManager下的方法
find (Class<T> entityClass,Object primaryKey):返回指定的 OID 对应的实体类对象,如果这个实体存在于缓存中,则返回一个被缓存的对象;否则会创建一个新的 Entity, 并加载数据库中相关信息;若 OID 不存在于数据库中,则返回一个 null。第一个参数为被查询的实体类类型,第二个参数为待查找实体的主键值。类似于Hibernate中的get方法。
getReference (Class<T> entityClass,Object primaryKey):与find()方法类似,不同的是:如果缓存中不存在指定的 Entity, EntityManager 会创建一个 Entity 类的代理,但是不会立即加载数据库中的信息,只有第一次真正使用此 Entity 的属性才加载,所以如果此 OID 在数据库不存在,getReference() 不会返回 null 值, 而是抛出EntityNotFoundException,类似于Hibernate中的load方法。
//类似于hibernate 中 session 的 get方法
//
调用find方法时就发sql
@Test
public void testFind() {
User user = entityManager.find(User.class, 1);
System.out.println("-------------------------");
System.out.println(user);
//类似于Hibernate 中 session 的 load方法
//使用的时候才发sql
@Test
public void testGetReference() {
User user = entityManager.getReference(User.class, 1);
System.out.println("-------------------------");
System.out.println(user);
persist (Object entity):用于将新创建的 Entity 纳入到 EntityManager 的管理。该方法执行后,传入 persist() 方法的 Entity 对象转换成持久化状态。
如果传入 persist() 方法的 Entity 对象已经处于持久化状态,则 persist() 方法什么都不做。
如果对游离状态的实体执行 persist() 操作,可能会在 persist() 方法抛出 EntityExistException(也有可能是在flush或事务提交后抛出),这一点也就是说persist方法不能保存游离态的实体,不同于Hibernate的是,HIbernate可以保存游离态的对象。
//添加方法类似于hibernate 的 session 中的 save方法
//不同点:但是不能添加存在id属性的实例(游离态), hibernate可以
@Test
public void testPersist() {
User user=new User();
user.setUserName("张三110");
user.setEmail("110@qq.com");
user.setBirth(new Date());
user.setCreateTime(new Date());
entityManager.persist(user);
remove (Object entity):删除实例。如果实例是被管理的,即与数据库实体记录关联,则同时会删除关联的数据库记录。
与HIbernate不同点在于JPA是不能删除在游离态的对象,HIbernate可以通过删除游离态的对象来影响到数据库中对应的数据,但是不推荐这样做。
//类似于Hibernate 中session 的delete方法
//不同点:remove不可以删除游离态的对象,
//hibernate可以删除游离态的对象,并且会影响到数据库中的数据
//hibernate可以操作(插入或删除)游离态的对象,而JPA不可以
@Test
public void testRemove() {
User user = entityManager.find(User.class, 1);
entityManager.remove(user);
merge (T entity):merge() 用于处理 Entity 的同步。即数据库的插入和更新操作,类似于HIbernate中的SaveOrUpdate方法。
* 总的来说: 类似于 hibernate Session 的 saveOrUpdate 方法.
//1. 若传入的是一个临时对象(没有id)
//会创建一个新的对象, 把临时对象的属性复制到新的对象中, 然后对新的对象执行持久化操作(insert). 所以
//新的对象中有 id, 但以前的临时对象中没有 id.
@Test
public void testMerge1(){
User user=new User();
user.setUserName("张三123");
user.setEmail("123@qq.com");
user.setBirth(new Date());
user.setCreateTime(new Date());
User user2 = entityManager.merge(user); // 返回持久化对象的引用
System.out.println(user.getId());
System.out.println(user2.getId());
//若传入的是一个游离对象, 即传入的对象有 OID.
//1. 若在 EntityManager 缓存中没有该对象
//2. 若在数据库中也没有对应的记录(先进行select查询)
//3. JPA 会创建一个新的对象, 然后把当前游离对象的属性复制到新创建的对象中
//4. 对新创建的对象执行 insert 操作. (没查到对应id的对象)
@Test
public void testMerge2(){
User user=new User();
user.setUserName("张三222");
user.setEmail("222@qq.com");
user.setBirth(new Date());
user.setCreateTime(new Date());
user.setId(100);
User user2 = entityManager.merge(user);
System.out.println(user.getId());
System.out.println(user2.getId());
//若传入的是一个游离对象, 即传入的对象有 OID.
//1. 若在 EntityManager 缓存中没有该对象
//2. 若在数据库中有对应的记录(先进行select查询)
//3. JPA 会查询对应的记录, 然后返回该记录对一个的对象, 再然后会把游离对象的属性复制到查询到的对象中.
//4. 对查询到的对象执行 update 操作. (查到对应id的对象)
@Test
public void testMerge3(){
User user=new User();
user.setUserName("张三333");
user.setEmail("333@qq.com");
user.setBirth(new Date());
user.setCreateTime(new Date());
user.setId(2);
User user2 = entityManager.merge(user);
System.out.println(user==user2); //false 游离态对象和返回的持久化对象不一致
//若传入的是一个游离对象, 即传入的对象有 OID.
//1. 若在 EntityManager 缓存中有对应的对象(使用find或者是getReference得到)
//2. JPA 会把游离对象的属性复制到查询到EntityManager 缓存中的对象中.
//3. EntityManager 缓存中的对象执行 UPDATE.
@Test
public void testMerge4(){
User user=new User();
user.setUserName("张三444");
user.setEmail("444@qq.com");
user.setBirth(new Date());
user.setCreateTime(new Date());
user.setId(2);
User user2 = entityManager.find(User.class, 2); //缓存对象
User user3 = entityManager.merge(user); //返回的持久化对象
System.out.println(user==user2); //false 游离态对象和缓存中的对象不一致
System.out.println(user==user3); //false 游离态对象和持久化对象不一致
System.out.println(user2==user3); //true user2和user3是同一个对象(缓存中已经存在)
映射关联关系
双向一对多及多对一映射
双向一对多关系中,必须存在一个关系维护端,在 JPA 规范中,要求 many 的一方作为关系的维护端(owner side),可以少发update语句。 one 的一方作为被维护端(inverse side)。 可以在 one 方指定 @OneToMany 注释并设置 mappedBy 属性(类似于HIbernate中的inverse属性),以指定它是这一关联中的被维护端,many 为维护端。 在 many 方指定 @ManyToOne 注释,并使用 @JoinColumn 指定外键名称。
Order类(多):
//多的一方
@ManyToOne
@JoinColumn(name="user_id") //外键名
public User getUser() {
return user;
User类(一):
//一的一方
@OneToMany(mappedBy="user") //使用mappedBy 放弃维护关系(一般都让一的一方放弃,多的一方维护关系)
public Set<Order> getOrders() {
return orders;
保存一对多(没有设置级联,建议先保存一再保存多,可以减少update语句)
//如果保存多对一关系时,如果没有设置级联,
//建议先保存一的一方,再保存多的一方(与保存的顺序有关),就不会出现多余的update
//设置级联为CascadeType.PERSIST(级联保存) 就可以通过保存Order来级联保存User
@Test
public void testPersistOrder() {
Order order1=new Order();
order1.setOrderName("FF1");
User user=new User();
user.setUserName("李四");
user.setBirth(new Date());
user.setCreateTime(new Date());
user.setEmail("666@qq.com");
order1.setUser(user);
entityManager.persist(user);
entityManager.persist(order1);
我们也可以通过设置级联,来保存一方的时候同时保存另一方:
@ManyToOne(cascade= {CascadeType.PERSIST})
@JoinColumn(name="user_id") //外键名
public User getUser() {
return user;
@Test
public void testPersistOrder2() {
Order order1=new Order();
order1.setOrderName("GG1");
User user=new User();
user.setUserName("李四光");
user.setBirth(new Date());
user.setCreateTime(new Date());
user.setEmail("666@qq.com");
order1.setUser(user);
entityManager.persist(order1);
删除(没有设置级联删除)
//删除时 删除关系维护端没有问题 删除被维护端报错(存在外键约束)
//如果想删除,就不要设置放弃维护关系(这里是不要设置mappedBy)
@OneToMany
@JoinColumn(name="user_id")
@Test
public void testRemoveOrder() {
//Order order = entityManager.find(Order.class, 3);
//entityManager.remove(order);
User user = entityManager.find(User.class, 16);
entityManager.remove(user);
因为我们在一的一方设置了mappedBy放弃维护关系,所以删除一的一方会出错(存在外键约束),解决办法我知道的有:
不要设置mappedBy属性,但是不注意保存先后顺序级就会出现多余的update
mappedBy和cascade= {CascadeType.REMOVE}属性配合使用,但是就只能级联删除了
手工将多方的外键改为null
还有没什么办法可以很好的解决这个放弃维护带来的删除问题?
其实仔细一想这个异常是存在一定道理的,因为我们都知道建表或插入数据的时候是先主表,后从表;而删除的时候想删除从表,再删除主表。如果我们不先处理多的一方,而直接删除一的一方,数据库是不允许的,因为外键字段的值必须在主表中存在,这个异常也在提示我们这样的操作存在问题
一对多双向关联查询
这里有一个规律:凡是以many结尾的都默认使用懒加载,而以one结尾的默认使用立即加载。如OneToMany 默认使用的就是延迟加载,而ManyToOne,OneToOne默认使用的是立即加载
我们可以在ManyToOne(以one结尾,默认使用立即加载)设置fetch属性:fetch=FetchType.LAZY ,将其设置为懒加载
//查询关系维护端(主控方)会发一条左外连接(left outer join)的查询sql
//设置了fetch 为lazy 延迟加载就不会连接查询了
@Test
public void testFindOrder() {
Order order = entityManager.find(Order.class, 3);
System.out.println(order.getUser());
双向多对多关联关系
在双向多对多关系中,我们必须指定一个关系维护端(owner side),可以通过 @ManyToMany 注释中指定 mappedBy 属性来标识其为关系维护端。
Item类:
@ManyToMany
@JoinTable(name="item_category",
joinColumns= {@JoinColumn(name="item_id")},
inverseJoinColumns= {@JoinColumn(name="categroy_id")})
public Set<Category> getCategories() {
return categories;
@JoinTable 设置中间表
name="中间表名称",
joinColumns={@joinColumn(name="本类的外键")}
inversejoinColumns={@JoinColumn(name="对方类的外键") }
Category类:
@ManyToMany(mappedBy="categories") //根据需求让一方放弃维护
public Set<Item> getItems() {
return items;
多对多删除
//多对多的删除
@Test
public void testManyToManyRemove(){
Item item = entityManager.find(Item.class, 1);
entityManager.remove(item);
没有设置级联的时候,删除时先根据删除方的id去中间表查询,查询到之后,先删除中间表,然后删除删除方的记录
双向一对一映射
基于外键的 1-1 关联关系:在双向的一对一关联中,需要在关系被维护端(inverse side)中的 @OneToOne 注释中指定 mappedBy(没有外键的一端,类似于主表),以指定是这一关联中的被维护端。同时需要在关系维护端(owner side 有外键存在的一端,类似于从表)建立外键列指向关系被维护端的主键列。
Dept和Manager是一对一关系,外键存在于Dept表中,所以然Manager放弃维护
Manager类:
@OneToOne(mappedBy="mgr") //放弃维护
public Dept getDept() {
return dept;
Dept类:
@OneToOne(fetch=FetchType.LAZY) //一对一(维护端)
@JoinColumn(name="mgr_id",unique=true)
public Manager getMgr() {
return mgr;
这里设置了一对一延迟加载,在使用到被维护端的时候,再去查询,而不是直接发一条left outer join 左外连接
//1.默认情况下, 若获取维护关联关系的一方, 则会通过左外连接获取其关联的对象.
//但可以通过 @OntToOne 的 fetch 属性(设置延迟加载)来修改加载策略.
//必须设置在维护端(主控方)的fetch属性才有效,设置在被维护端没用
@Test
public void testOneToOneFind(){
Dept dept = entityManager.find(Dept.class, 1);
System.out.println(dept.getDeptName());
System.out.println(dept.getMgr().getClass().getName());
使用二级缓存
在Hibernate中一级缓存是session级别的缓存,是默认带的且开起的,而二级缓存是sessionFactory级别的缓存,是跨session的。同理,JPA中一级缓存是EntityManager级的缓存,而二级缓存是EntityManagerFactory级别的缓存。
使用二级缓存需要配置其实现产品,这里使用的是ehcache。
先在persistence.xml文件中配置:
配置二级缓存的策略
ALL:所有的实体类都被缓存
NONE:所有的实体类都不被缓存.
ENABLE_SELECTIVE:标识 @Cacheable(true) 注解的实体类将被缓存
DISABLE_SELECTIVE:缓存除标识 @Cacheable(false) 以外的所有实体类
UNSPECIFIED:默认值,JPA 产品默认值将被使用
<!--ENABLE_SELECTIVE策略 在需要二级缓存的实体类上使用@Cacheable(true) -->
<shared-cache-mode>ENABLE_SELECTIVE</shared-cache-mode>
<!-- 二级缓存相关配置(使用的是hibernate二级缓存,缓存产品为ehcache) -->
<property name="hibernate.cache.use_second_level_cache" value="true"/>
<property name="hibernate.cache.region.factory_class" value="org.hibernate.cache.ehcache.EhCacheRegionFactory"/>
<!-- 查询缓存 -->
<property name="hibernate.cache.use_query_cache" value="true"/>
这里有个查询缓存,等到JPQL之后再说.
<shared-cache-mode> 节点:若 JPA 实现支持二级缓存,该节点可以配置在当前的持久化单元中是否启用二级缓存,可配置如下值:
ALL:所有的实体类都被缓存
NONE:所有的实体类都不被缓存.
ENABLE_SELECTIVE:标识 @Cacheable(true) 注解的实体类将被缓存
DISABLE_SELECTIVE:缓存除标识 @Cacheable(false) 以外的所有实体类
UNSPECIFIED:默认值,JPA 产品默认值将被使用。
我们设置的配置是ENABLE_SELECTIVE,所以一定要在需要二级缓存的类上使用 @Cacheable(true) 注解标识
我们还需要在src下放入一个ehcache的配置文件:ehcache.xml
<ehcache>
指定一个目录:当 EHCache 把数据写到硬盘上时, 将把数据写到这个目录下.
如:<diskStore path="d:\\tempDirectory"/>
<diskStore path="java.io.tmpdir"/>
<!--默认的缓存配置-->
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="true"
设定具体的命名缓存的数据过期策略。每个命名缓存代表一个缓存区域
缓存区域(region):一个具有名称的缓存块,可以给每一个缓存块设置不同的缓存策略。
如果没有设置任何的缓存区域,则所有被缓存的对象,都将使用默认的缓存策略。即:<defaultCache.../>
Hibernate 在不同的缓存区域保存不同的类/集合。
对于类而言,区域的名称是类名。如:cn.lynu.entity.Dept
对于集合而言,区域的名称是类名加属性名。如cn.lynu.entity.Dept.emps
name: 设置缓存的名字,它的取值为类的全限定名或类的集合的名字
maxElementsInMemory: 设置基于内存的缓存中可存放的对象最大数目
eternal: 设置对象是否为永久的, true表示永不过期,
此时将忽略timeToIdleSeconds 和 timeToLiveSeconds属性; 默认值是false
timeToIdleSeconds:设置对象空闲最长时间,以秒为单位, 超过这个时间,对象过期。
当对象过期时,EHCache会把它从缓存中清除。如果此值为0,表示对象可以无限期地处于空闲状态。
timeToLiveSeconds:设置对象生存最长时间,超过这个时间,对象过期。
如果此值为0,表示对象可以无限期地存在于缓存中. 该属性值必须大于或等于 timeToIdleSeconds 属性值
overflowToDisk:设置基于内存的缓存中的对象数目达到上限后,是否把溢出的对象写到基于硬盘的缓存中
<cache name="sampleCache1"
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="300"
timeToLiveSeconds="600"
overflowToDisk="true"
<cache name="sampleCache2"
maxElementsInMemory="1000"
eternal="true"
timeToIdleSeconds="0"
timeToLiveSeconds="0"
overflowToDisk="false"
</ehcache>
开始测试吧:
//开启二级缓存
@Test
public void testSecondLevelCache(){
User user1 = entityManager.find(User.class, 2);
//提交并关闭事务
transaction.commit();
entityManager.close();
//开启新事务
entityManager=entityManagerFactory.createEntityManager();
transaction=entityManager.getTransaction();
transaction.begin();
User user2 = entityManager.find(User.class, 2);
这里查询之后提交并关闭事务和EntityManager,用一个新的EntityManager和新的事务进行操作,在没有开二级缓存的时候,会发两条相同的select语句,成功开启二级缓存之后,就只有第一次的一条select了。
类似于HQL,JPQL也是面向对象的查询语句,也可以完成CRUD操作,但是写法上有点不同,不同点在于查询上,来看一条JPQL语句:
SELECT u from User u
再来看HQL的写法:
from User
可以看到JPQL的查询需要将select关键字和需要得到的字段写出来(好在需要得到的字段可以是一个对象),运行这些语句的都是Query接口,只是要注意是不同的包下的。
Query接口的主要方法:
int executeUpdate() 用于执行update或delete语句。
List getResultList() 用于执行select语句并返回结果集实体列表。
Object getSingleResult() 用于执行只返回单个结果实体的select语句。
Query setFirstResult(int startPosition) 用于设置从哪个实体记录开始返回查询结果。
Query setMaxResults(int maxResult) 用于设置返回结果实体的最大数。与setFirstResult结合使用可实现分页查询。
setHint(String hintName, Object value) 设置与查询对象相关的特定供应商参数或提示信息。参数名及其取值需要参考特定 JPA 实现库提供商的文档。如果第二个参数无效将抛出IllegalArgumentException异常。
setParameter(int position, Object value) 为查询语句的指定位置参数赋值。Position 指定参数序号,value 为赋给参数的值。
setParameter(String name, Object value) 为查询语句的指定名称参数赋值。
@Test
public void testHelloJPQL(){
String jpql="SELECT u from User u";
Query query = entityManager.createQuery(jpql);
List list = query.getResultList(); //对应Hibernate中Query接口的list()方法
System.out.println(list.size());
我还记得Hibernate的占位符是从0开始的,而JDBC是从1开始的,总是记这些东西很麻烦,所以在JPA中可以指定占位符从几开始:
@Test
public void testHelloJPQL2(){
String jpql="select u from User u where id>?1";
Query query = entityManager.createQuery(jpql);
//
占位符的索引是从 0 开始,可以指定从几开始,如?1 就是从1开始
query.setParameter(1, 3);
List list = query.getResultList();
System.out.println(list.get(3));
还可以使用名称占位:
@Test
public void testHelloJPQL3(){
String jpql="select u from User u where id>:id";
Query query = entityManager.createQuery(jpql);
//使用名称占位
query.setParameter("id", 3);
List list = query.getResultList();
System.out.println(list.get(3));
在JPQL中还可以使用Order by
Order by子句用于对查询结果集进行排序。和SQL的用法类似,可以用 “asc“ 和 "desc“ 指定升降序。如果不显式注明,默认为升序。
select o from Orders o order by o.id
select o from Orders o order by o.address.streetNumber desc
select o from Orders o order by o.customer asc, o.id desc
group by子句与聚合查询
//分组查询(查询 order 数量等于 2 的那些 User)
@Test
public void testGroupBy(){
String jpql="select o.user from Order o group by o.user having count(o.id)=?1";
List list = entityManager.createQuery(jpql).setParameter(1, 2).getResultList();
System.out.println(list);
常用的聚合函数主要有 AVG、SUM、COUNT、MAX、MIN 等,它们的含义与SQL相同。
Query query = entityManager.createQuery(
"select max(o.id) from Orders o");
Object result = query.getSingleResult();
Long max = (Long)result;
JPQL 也支持和 SQL 中类似的关联语法。如: left out join fetch, right out join fetch 。eft out join,如left out join fetch是以符合条件的表达式的左侧为主。
* JPQL 的关联查询(left outer join fetch)同 HQL 的关联查询.
@Test
public void testLeftOuterJoinFetch(){
String jpql="select u from User u left outer join fetch u.orders where u.id=?1";
List<User> list = entityManager.createQuery(jpql).setParameter(1, 16).getResultList();
System.out.println(list.get(0).getOrders());
左外的右边使用的是User类中的orders属性表示另一张表,而且要加上fetch语句,才是一个真正左外连接,要不会报sql异常
JPQL也支持子查询,在 where 或 having 子句中可以包含另一个查询。当子查询返回多于 1 个结果集时,它常出现在 any、all、exists表达式中用于集合匹配查询。它们的用法与SQL语句基本相同。
* JPQL子查询
@Test
public void testSubQuery(){
//查询所有 User 的 userName 为 赵六 的 Order
String jpql="select o from Order o where o.user=(select u from User u where u.userName=?1)";
List list = entityManager.createQuery(jpql).setParameter(1, "赵六").getResultList();
System.out.println(list);
JPQL函数
JPQL提供了以下一些内建函数,包括字符串处理函数、算术函数和日期函数。如字符串处理函数:
concat(String s1, String s2):字符串合并/连接函数。
substring(String s, int start, int length):取字串函数。
trim([leading|trailing|both,] [char c,] String s):从字符串中去掉首/尾指定的字符或空格。
lower(String s):将字符串转换成小写形式。
upper(String s):将字符串转换成大写形式。
length(String s):求字符串的长度。
locate(String s1, String s2[, int start]):从第一个字符串中查找第二个字符串(子串)出现的位置。若未找到则返回0。
//使用 jpql 内建的函数
@Test
public void testJpqlFunction(){
String jpql="select upper(o.orderName) from Order o";
List list = entityManager.createQuery(jpql).getResultList();
System.out.println(list);
算术函数主要有 abs、mod、sqrt、size 等。Size 用于求集合的元素个数。
日期函数主要为三个,即 current_date、current_time、current_timestamp,它们不需要参数,返回服务器上的当前日期、时间和时戳。
刚才说了一个查询缓存的问题,是因为使用Query接口查询的结果并不会放入一级缓存中,因为其未与EntityManager关联,HIbernate中也如此,所以需要配置那个查询缓存,注意:查询缓存需要依赖于二级缓存。
需要在需要查询缓存的时候使用: query.setHint(QueryHints.HINT_CACHEABLE, true);
//使用 hibernate 的查询缓存.(query接口查询(hql)的结果不会放到缓存中,需要配置查询缓存)
@Test
public void testQueryCache(){
String jpql="SELECT u from User u";
Query query = entityManager.createQuery(jpql);
//查询缓存(org.hibernate.jpa.QueryHints;)
query.setHint(QueryHints.HINT_CACHEABLE, true);
List list = query.getResultList();
System.out.println(list.size());
//没有配置的时候多次相同的sql查询会发多条相同的sql
//配置查询缓存之后,相同查询就发一条sql
//(配置文件中要配置hibernate.cache.use_query_cache)
//query = entityManager.createQuery(jpql);
//查询缓存(只要给query使用jpql就需要setHint)
//query.setHint(QueryHints.HINT_CACHEABLE, true);
list = query.getResultList();
System.out.println(list.size());
整合Spring
Spring整合JPA之后,就不需要persistence.xml文件了,需要在applicationContext.xml文件中配置EntityManagerFactory和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:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:mvc="http://www.springframework.org/schema/mvc"
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/tx http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!-- 配置 C3P0 数据源 -->
<context:property-placeholder location="classpath:jdbc.properties"/>
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="user" value="${jdbc.user}"></property>
<property name="password" value="${jdbc.password}"></property>
<property name="driverClass" value="${jdbc.driverClass}"></property>
<property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property>
</bean>
<!-- 配置 EntityManagerFactory -->
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<!-- 配置数据源 -->
<property name="dataSource" ref="dataSource"></property>
<!-- 配置 JPA 提供商的适配器. 可以通过内部 bean 的方式来配置 -->
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"></bean>
</property>
<!-- 配置实体类所在的包 -->
<property name="packagesToScan" value="cn.lynu.model"></property>
<!-- 配置 JPA 的基本属性. 例如 JPA 实现产品的属性 -->
<property name="jpaProperties">
<props>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.format_sql">true</prop>
<prop key="hibernate.hbm2ddl.auto">update</prop>
</props>
</property>
</bean>
<!-- 配置 JPA 使用的事务管理器 -->
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"></property>
</bean>
<!-- 配置支持基于注解是事务配置 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
<!-- 配置自动扫描的包(组件扫描) -->
<context:component-scan base-package="cn.lynu"></context:component-scan>
</beans>