2016-6-26

JPA 的标准

先了解一下 JPA

JPA就是Java Persistence API的简称, 是JDK 5.0 注解或XML描述对象-关系表的映射关系, 并将运行期的实体对象持久化到数据库中;

jpa具有什么优势?

  1. 标准化 JPA 是 JCP 组织发布的 Java EE 标准之一, 因此任何声称符合 JPA 标准的框架都遵循同样的架构, 提供相同的访问API, 这保证了基于JPA开发的企业应用能够经过少量的修改就能够在不同的JPA框架下运行;
  2. 容器级特性的支持 JPA框架中支持大数据集, 事务, 并发等容器级事务, 这使得 JPA 超越了简单持久化框架的局限, 在企业应用发挥更大的作用;
  3. 简单方便 JPA的主要目标之一就是提供更加简单的编程模型: 在JPA框架下创建实体和创建Java 类一样简单, 没有任何的约束和限制, 只需要使用 javax.persistence.Entity进行注释, JPA的框架和接口也都非常简单, 没有太多特别的规则和设计模式的要求, 开发者可以很容易的掌握; JPA基于非侵入式原则设计, 因此可以很容易的和其它框架或者容器集成;
  4. 查询能力 JPA的查询语言是面向对象而非面向数据库的, 它以面向对象的自然语法构造查询语句, 可以看成是Hibernate HQL的等价物; JPA定义了独特的JPQL(Java Persistence Query Language), JPQL是EJB QL的一种扩展, 它是针对实体的一种查询语言, 操作对象是实体, 而不是关系数据库的表, 而且能够支持批量更新和修改, JOIN, GROUP BY, HAVING 等通常只有 SQL 才能够提供的高级查询特性, 甚至还能够支持子查询;
  5. 高级特性 JPA 中能够支持面向对象的高级特性, 如类之间的继承, 多态和类之间的复杂关系, 这样的支持能够让开发者最大限度的使用面向对象的模型设计企业应用, 而不需要自行处理这些特性在关系数据库的持久化;

hibernate 就是实现jpa的一个框架, 它实现了JPA的,(注意,并非全部, 它有自己的一些注解,看hibernate文档.)

JPA两种配置方式

  1. 基于xml
  2. 基于注解

spring-boot-starter-data-jpa

JSR-338 标准

JSR 338 主要定义了如何通过普通的Java domain进行关系数据库的操作及映射, 其中主要概念包含如下:

Entity

  • @Entity 注解的类
  • 必须有一个无参的public 或 protected的构造方法
  • 不能是final类, 且不能有final方法或final变量
  • 一个Entity类通常与数据库的一张表进行对应
  • 一个Entity实例表现为数据库的一条数据
  • 对Entity的操作即对数据库的操作
  • 生命周期包含初始, 托管, 释放, 消亡

EntityManager

  • 对Entity持久化操作的主要对象
  • 通过EntityManagerFactory获取实例
  • 一个实例代表一个数据库连接
  • 每个线程拥有自己的EntityManager实例
  • 主要方法有persist, remove, merge, createQuery, find
  • 可使用@PersistenceContext注入

EntityManagerFactory

  • 创建EntityManager的工厂
  • EntityManagerFactory的创建成本很高, 对于给定的数据库, 系统应该只创建一个与之关联的Factory
  • 可使用@PersistenceUnit注入
  • EntityTransaction
  • 表示数据库事务, 在增, 删, 改时使用
  • 可通过EntityManager.getTransaction()获取

Persistence Context

  • 维护一组托管状态的Entity实例
  • 与EntityManager是相关联的

Persistence Unit

  • 一组Entity及相关设置的逻辑单元
  • 定义创建EntityManagerFactory所需要的参数
  • 通过persistence.xml定义或者通过一个PersistenceUnitInfo对象

总结一下, 通过Persistence Unit创建EntityManagerFactory, 再通过EntityManagerFactory获取EntityManager;

与 Hibernate

EntityManagerFactory: 创建和管理多个EntityManager实例 //对应hibernate(SessionFactory) EntityManager: 接口, 管理对象的操作(create, update, delete, Query) //对应hibernate(Session) Entity: 持久化对象, 在数据库中以record存储//实体 EntityTransaction: 与EntityManager一对一// Persistence: 包含获取 EntityManagerFactory 实例的静态方法 Query: 获取满足creteria的关系对象(relational object)//对应 hibernate Query

  • 一个JPQA查询的例子
EntityManager em = ...;
CriteriaBuilder cb = em.getCriteriaBuilder();
 
CriteriaQuery<Entity class> cq = cb.createQuery(Entity.class);
Root<Entity> from = cq.from(Entity.class);
 
cq.select(Entity);
TypedQuery<Entity> q = em.createQuery(cq);
List<Entity> allitems = q.getResultList();
 
 

获取JPA 的实现

JPA 提供unwrap(cls)方法拆开, 获得它的实现对象

例如:entityManagerFactory 对象, 对应 hibernate 的 SessionFactory 实现, 以下代码可以获得: SessionFactory sessionFactory = entityManagerFactory.unwrap(SessionFactory.class);

实例代码

@Autowired 
private EntityManagerFactory entityManagerFactory;//jpa规范, spring自动创建
 
SessionFactory sessionFactory = entityManagerFactory.unwrap(SessionFactory.class);//展开获得 hibernate  SessionFactory
Session session = sessionFactory.openSession();//展开获得 hibernate  Session
Query<Sl_collection_data> query = session.createQuery(hqlSelect+hqlWhere.toString(),Sl_collection_data.class);//展开获得 hibernate   Query<?>
query.setFirstResult(pageSize*(page-1));
query.setMaxResults(pageSize);
List<Sl_collection_data> lst = query.list();
session.close();

JPA API 实例

JPA JPQL 查询

官方文档 Chapter 4 - JPA Queries (JPQL / Criteria)

hiberante JPA api

语法

SELECT clause (JPQL / Criteria API)

DELETE Queries in JPA/JPQL

UPDATE SET Queries in JPA/JPQL

API

分页

@PersistenceContext//可注入已经打开的 EntityManager ,用来为每个线程创建一个EntityManager, 免于手动管理
// EntityManager entityManager = entityManagerFactory.createEntityManager();
protected EntityManager entityManager;
 
Query query = entityManager.createQuery(jqlSelect+hqlWhere.toString());
Query query2 = entityManager.createQuery(jqlCount+hqlWhere.toString());
...
query.setFirstResult(pageSize*(page-1));
query.setMaxResults(pageSize);
List<Sl_collection_data> lst = query.getResultList();
lpd.setCount(  (Long) query2.getSingleResult());
 

JPA 标准查询(Criteria API Queries)

官方文档

/**
CriteriaBuilder 安全查询创建工厂, 创建CriteriaQuery, 创建查询具体具体条件Predicate, 用来 组建 count, group by , 和组建 and , or 条件操作
*/
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
/**
CriteriaQuery 安全查询主语句, 泛型是查询返回的类型
注意 BookCrq 它需要一个所有参数且相同顺序的 构造函数
*/
CriteriaQuery<BookCrq> criteriaQuery = cb.createQuery(BookCrq.class);
 
/**
from 实体
*/
Root<Book> book=  criteriaQuery.from(Book.class);
//    	Root<BookType> bookType = criteriaQuery.from(BookType.class);
/**
join 一张表, 另外还有joinmap, joinset ...
**/
//left join BookType as bookType on book.bookType.id = bookType.id
Join<Book, BookType> bookType = book.join("bookType", JoinType.LEFT);
// (on.. and ?) book.name = bookType.name 
Predicate predicate = criteriaQuery.equal(bookType.get("name"),book.get("name"))//额外 join条件
//(book.id as id, bookType.name as name) 
criteriaQuery.multiselect( 	book.get("id").alias("id"), bookType.get("name").alias("name"));
 
criteriaQuery.where(predicate)//添加join 条件
TypedQuery<BookCrq>  query = entityManager.createQuery(criteriaQuery);
List res = query.getResultList();
 
//以上翻译过来就是
//`SELECT new BookCrq(book.id as id, bookType.name as name) from Book as book left join BookType as bookType on book.id = bookType.id `
 
 

注意 BookCrq 必须有全参构造函数

简而言之

  • 把 CriteriaQuery 理解为查询主语句 build, 组建 select , from , where , order 等 CriteriaQuery<BookCrq> criteriaQuery = cb.createQuery(BookCrq.class); 这个泛型指定了查询返回结果, 组建select字段的时候可以指定,属性映射别名 criteriaQuery.multiselect( book.get("id").alias("id"), bookType.get("name").alias("name"));

  • 把 CriteriaBuilder 理解为创建条件(Predicate)的 build;

  • 把 Predicate 理解一个条件, 需要添加到 CriteriaQuery里面. criteriaQuery.and(predicate)//添加条件

  • 把 Root<?> 理解为主表的路径 ‘别名’ bookType.get("name"),book.get("name")

  • 把 Join 理解为join表的路径 ‘别名’

CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<T> q = cb.createQuery(entryClass);//
Root<T> root = q.from(entryClass);
q.select(root);
TypedQuery query = entityManager.createQuery(q);

踩坑指南

criteriaQuery 的 排序(order by), 条件(where) 必须在entityManager.createQuery(criteriaQuery);之前 build完, 否则无效!!!

JPA CASE 函数调用

JPA 如果在 Select,或者其它地方,使用SQL的case?

正解是使用 criteriaBuilder.selectCase()

cb.selectCase() //
    .when(cb.equal(user.get("address").get("country"), country),
            cb.selectCase()
            .when(cb.equal(user.get("address").get("city"), city), cb.literal(true))
            .otherwise(cb.literal(false))
   ))
    .when(cb.equal(user.get("address").get("country"), "India"), cb.literal(true))
    .otherwise(cb.literal(false)); 
 
 
// 类似语句
SELECT CASE
    WHEN user.country = :country AND user.city = :city THEN TRUE
    WHEN user.country = :country AND user.city <> :city THEN FALSE
    WHEN user.country = 'India' THEN TRUE
    ELSE FALSE
END

参考

JPA 元组查询

返回类似Map的结果

 
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Tuple> criteriaQuery =  cb.createTupleQuery();
 
Root<Book> book=  criteriaQuery.from(Book.class);
//    	Root<BookType> bookType = criteriaQuery.from(BookType.class);
Join<Book, BookType> bookType = book.join("bookType", JoinType.LEFT);
criteriaQuery.multiselect( 	book.get("id").alias("id"), bookType.get("name").alias("name"));
TypedQuery<Tuple>  query = entityManager.createQuery(criteriaQuery);
List<Tuple>  lst = query.getResultList();
 
//将元组 转成Map
List<Map> content = lst.stream().map( data->{
	final Map<String, Object> map = new HashMap<String, Object>();
	//e.getElements().stream().collect(Collectors.toMap( e.getElements().forEach(e->.get); ,  t->t.length()));
	data.getElements().forEach(e->{
		String alias = e.getAlias();
		map.put(alias,  data.get(alias));
	});
	return map;
}).collect(Collectors.toList());

Spring Query by Example API 使用

注意: 此API仅适用简单的查询, 不支持嵌套条件

ExampleMatcher exampleMatcher = ExampleMatcher.matchingAll();
//多条件  account='test' and username='zw'
SysUser entity=new SysUser();
entity.setAccount("test");
entity.setUserName("zw");
ExampleMatcher matcher = ExampleMatcher.matching()
		.withMatcher("account", ExampleMatcher.GenericPropertyMatchers.contains())
		.withMatcher("userName", ExampleMatcher.GenericPropertyMatchers.contains());//like
Example<SysUser> example = Example.of(entity,matcher);
 

JPA 生命周期事件

官方文档 JPA Lifecycle Events

//@EntityListeners(value = {AuditListener.class}) 或者通过 EntityListeners 注解指定对象监听
@Entity
public static class MyEntityWithCallbacks {
    @PrePersist void onPrePersist() {}
    @PostPersist void onPostPersist() {}
    @PostLoad void onPostLoad() {}
    @PreUpdate void onPreUpdate() {}
    @PostUpdate void onPostUpdate() {}
    @PreRemove void onPreRemove() {}
    @PostRemove void onPostRemove() {}
}
 

The annotation specifies when the callback method is invoked:

@PrePersist - before a new entity is persisted (added to the EntityManager). @PostPersist - after storing a new entity in the database (during commit or flush). @PostLoad - after an entity has been retrieved from the database. @PreUpdate - when an entity is identified as modified by the EntityManager. @PostUpdate - after updating an entity in the database (during commit or flush). @PreRemove - when an entity is marked for removal in the EntityManager. @PostRemove - after deleting an entity from the database (during commit or flush).

JPA 联合主键&集合的映射

在集合元素的一方使用`@Embeddable`注解表示,它是可嵌入式的元素
 
`@Embeddable`
class Sl_collention_data_original{
    @Column(name= "datatime_")
    ...
}
 
在引用的一方
 
@ElementCollection //targetClass=Sl_collention_data_original.class指定目标类型,  基本类型使用(targetClass=java.lang.Float.class)
@CollectionTable(name = "sl_collention_data_original")//指定集合的表名
//(基本类型)使用 @Column(name="original_") 指定集合表的列名
@OrderColumn(name="index_")//如果是List 使用 OrderColumn 指定索引列名, 以保证顺序
 

JPA 的注解

注解 说明 @Entity 将Java类映射到数据库表 @Table 指定实体类与数据库表之间的映射关系 @Id 标识实体类的主键属性 @GeneratedValue 指定主键的生成策略 @Column 指定实体属性与数据库表字段之间的映射关系 @Transient 标识属性不需要持久化到数据库 @OneToMany 建立实体类之间的一对多关系 @ManyToOne 建立实体类之间的多对一关系 @ManyToMany 建立实体类之间的多对多关系 @JoinColumn 指定实体关联关系中的外键列 @Embedded 将嵌入式对象映射到数据库表 @Embeddable 标识一个可嵌入的类 @Enumerated 指定枚举类型的持久化方式 @Temporal 指定日期时间属性的持久化方式 @Version 实现乐观锁控制 @NamedQuery 在实体类上定义一个查询 @NamedQueries 在实体类上定义多个查询 @NamedNativeQuery 在实体类上定义一个原生SQL查询 @NamedNativeQueries 在实体类上定义多个原生SQL查询 @JoinTable 指定多对多关联关系中的关联表 @JoinColumns 指定实体类之间的关联关系中的多个外键列 @JoinFetch 指定在查询关联实体时是否使用立即加载 @AttributeOverride 覆盖继承关系中父类属性的映射信息 @AttributeOverrides 覆盖继承关系中父类多个属性的映射信息

常见的注解

用于指示某个属性不需要持久化到数据库。 标记为 @Transient 的属性将被忽略。

实体父类 @MappedSuperclass

@MappedSuperclass

使用方式:

  1. @MappedSuperclass 注解使用在一个实体类父类上, 来标识这个父类;
  2. @MappedSuperclass 标识的类表示其不能映射到数据库表, 因为其不是一个完整的实体类, 但它所拥有的属性能够映射到其子类所在的表中;
  3. @MappedSuperclass 标识的类不能有@Entity和@Table注解;
//SuperEntity
@MappedSuperclass
public class SuperEntity {
	protected Long id;
	@Id
	@GeneratedValue(strategy=GenerationType.IDENTITY)//strategy=GenerationType.IDENTITY 自增, 默认有序列表 
	@Column(name = "id")
	public Long getId() {
		return id;
	}
	public void setId(Long id) {
		this.id = id;
	}
	@Override
	public String toString() {
		 return "{\"id\":\""+this.getId()+"\"}";
	}
}
 
//Entity
@Entity
@Table(name = "...") 
public class Sl_collection_data extends SuperEntity{
 
 
//JpaRepository
@Repository
public interface Sl_collention_dataRepository extends JpaRepository<Sl_collection_data, Long>{
}
 
 
//SupperService
public class SupperService <M,R extends JpaRepository<M,I>,I>{
	
	@Autowired
	protected R jpaRepository;
	@PersistenceContext
	protected EntityManager entityManager;
	public R getJpaRepository() {
		return jpaRepository;
	}
	
}
 
//Service
 
@Service
public class Sl_collention_dataService extends SupperService<Sl_collection_data, Sl_collention_dataRepository , Long> {
}

ID生成策略

  • @GeneratedValue

@GeneratedValue(strategy=XX) strategy属性提供四种值:

  1. AUTO 主键由程序控制, 是默认选项 ,不设置就是这个
  2. IDENTITY 主键由数据库生成, 采用数据库自增长, Oracle不支持这种方式
  3. SEQUENCE 通过数据库的序列产生主键, MYSQL 不支持
  4. Table 提供特定的数据库产生主键, 该方式更有利于数据库的移植

MySQL 自增主键的用法 @GeneratedValue(strategy=GenerationType.IDENTITY)

@GeneratedValue

javax.persistence.GeneratedValue JPA 标准

  • generator 属性值:

hibernate 提供多种主键生成策略, 有点是类似于JPA, 有的是hibernate特有:   native: 对于 oracle 采用 Sequence 方式, 对于MySQL 和 SQL Server 采用identity(自增主键生成机制), native就是将主键的生成工作交由数据库完成, hibernate不管(很常用);   uuid: 采用128位的uuid算法生成主键, uuid被编码为一个32位16进制数字的字符串; 占用空间大(字符串类型);   hilo: 使用hilo生成策略, 要在数据库中建立一张额外的表, 默认表名为hibernate_unique_key,默认字段为integer类型, 名称是next_hi(比较少用);   assigned: 在插入数据的时候主键由程序处理(很常用), 这是 <generator>元素没有指定时的默认生成策略; 等同于JPA中的AUTO;   identity: 使用SQL Server 和 MySQL 的自增字段, 这个方法不能放到 Oracle 中, Oracle 不支持自增字段, 要设定sequence(MySQL 和 SQL Server 中很常用);   等同于JPA中的INDENTITY;   select: 使用触发器生成主键(主要用于早期的数据库主键生成机制, 少用);   sequence: 调用底层数据库的序列来生成主键, 要设定序列名, 不然hibernate无法找到;   seqhilo: 通过hilo算法实现, 但是主键历史保存在Sequence中, 适用于支持 Sequence 的数据库, 如 Oracle(比较少用)  increment: 插入数据的时候hibernate会给主键添加一个自增的主键, 但是一个hibernate实例就维护一个计数器, 所以在多个实例运行的时候不能使用这个方法;   foreign: 使用另外一个相关联的对象的主键; 通常和<one-to-one>联合起来使用;   guid: 采用数据库底层的guid算法机制, 对应MYSQL的uuid()函数, SQL Server的newid()函数, ORACLE的rawtohex(sys_guid())函数等;   uuid.hex: 看uuid, 建议用uuid替换;   sequence-identity: sequence策略的扩展, 采用立即检索策略来获取sequence值, 需要JDBC3.0和JDK4以上(含1.4)版本 

  • strategy 属性值:

参考: javax.persistence.GenerationType

@GenericGenerator

@GenericGenerator 并不是jpa标准, 由hibernate 扩展的主键策略生成器.

或者说 GeneratedValue 指定生成方式, GeneratedValue 实现具体生成值

@GeneratedValue(generator="system-uuid")
@GenericGenerator(name="system-uuid", strategy = "uuid")

org.hibernate.id.IdentifierGeneratorFactory 生成工厂(这个是接口..) 默认是org.hibernate.id.factory.internal.DefaultIdentifierGeneratorFactory注册了一下可直接用的 generator

public DefaultIdentifierGeneratorFactory() {
    register( "uuid2", UUIDGenerator.class);
    register( "guid", GUIDGenerator.class);			// can be done with UUIDGenerator + strategy
    register( "uuid", UUIDHexGenerator.class);			// "deprecated" for new use
    register( "uuid.hex", UUIDHexGenerator.class); 	// uuid.hex is deprecated
    register( "assigned", Assigned.class);
    register( "identity", IdentityGenerator.class);
    register( "select", SelectGenerator.class);
    register( "sequence", SequenceStyleGenerator.class);
    register( "seqhilo", SequenceHiLoGenerator.class);
    register( "increment", IncrementGenerator.class);
    register( "foreign", ForeignGenerator.class);
    register( "sequence-identity", SequenceIdentityGenerator.class);
    register( "enhanced-sequence", SequenceStyleGenerator.class);
    register( "enhanced-table", TableGenerator.class);
}

@GeneratedValue

自定义生成策略

  1. 注解
@Id
@GenericGenerator(
    name = "idGenerator",//// 给主键生成策略起个名字, 随意起名
    strategy = "net.jk.cloud.dataset.domain.IdGeneratorConfig"  // 自定义的主键生成类全名面的名称一致
   )
@GeneratedValue(generator = "idGenerator")// 策略名称, 必须和上
private String id;
 
  1. 实现org.hibernate.id.IdentifierGenerator的接口,
public static class IdGeneratorConfig implements IdentifierGenerator {
        @Override
        public Serializable generate(SharedSessionContractImplementor session, Object object)
            throws HibernateException {
        return IdUtil.simpleUUID();
        }
}

实现手动设置ID

JPA 2.0 Specification

Entity class must be annotated with the Entity annotation. Entity class must have a no-arg constructor. Entity class must not be final Entity class must implement the Serializable interfaces. Entity class must have a unique, immutable ID

JPA 标准是实体必须有具有 ID, 可以曲线救国,使用自定义生成策略.

public class ManualIdGenerator implements IdentifierGenerator {
	@Override
	public Serializable generate(SharedSessionContractImplementor session, Object object) throws HibernateException {
		return 	(Serializable) BeanUtil.getFieldValue(object, "id");
	}
}
////////////
public class UserPrintTemp implements Serializable {
 
@Id
@GenericGenerator(
		name = "manualGenerator",
		strategy = "net.jk.cloud.data.jpa.ManualIdGenerator"
	   )
@GeneratedValue(generator = "manualGenerator")
@Column(name = "ID")
private String id;
 

审计注解

Spring Data JPA为我们提供了审计功能的架构实现, 提供4个注解专门解决这件事情:

@CreatedBy: 哪个用户创建的 @CreatedDate: 创建时间 @LastModifiedBy: 修改实体的用户 @LastModifiedDate: 最后一次修改时间

@UpdateTimestamp @CreationTimestamp

它是基于 @EntityListeners 实现的, AuditingEntityListener通过委托设计模式, 委托AuditingHandler进行处理, AuditingHandler里面是根据ID和Version来判断我们的对象是新增还是更新, 从而来更改时间字段和User字段而User字段是通过AuditorAware的实现类来取的, 并且AuditorAware没有默认实现类, 实现类必须我们自己来定义, 否则报错

@Data
@Entity(name = "t_user")
@EntityListeners(AuditingEntityListener.class)
public class SystemUser implements Serializable {
 
 
 @CreatedBy
    private Integer createUserId;
 
 
}

基于注解 (配置关联关系)

 
@Entity//如果我们当前这个bean要设置成实体对象, 就需要加上Entity这个注解
@Table(name="t_user")//设置数据库的表名
public User{
	private String id;
	private String name;
	private String pwd;
	private String ver;
	
	@Id //定义为数据库的主键ID
	@GeneratedValue// 定义ID生成策略,这里需要根据数据库不同配置其他属性
	public String getId(){
		...
	}
 
	@Column(name="name_")//Column中的name属性对应了数据库的该字段名字, 里面还有其他属性, 例如length, nullable等等	
	public String getName(){
		...
	}
 
	@Version//该列用于乐观锁版本控制 
	@Column(name="VER_")    
	public String getVer(){
		...
	}
 
	@Transient//修饰不想持久保存的属性
	public int getAge(){
		...
	}
 
}

一对多的映射(one-to-many)

//一方
@Entity
@Table(name="t_classroom")
public class ClassRoom
{
	@OneToMany(mappedBy="room",cascade=CascadeType.ALL)//OneToMany指定了一对多的关系
	private Set<Student> students;
 
	
}
 
//多方
@Entity
@Table(name="t_tudent")
public class Student
{
    @ManyToOne() //ManyToOne指定了多对一的关系, 
	//通过 JoinColumn 的name属性指定了外键的名称 rid
    @JoinColumn(name="rid")
    private ClassRoom room;
 
}

mappedBy 属性

mappedBy 声明维护方, 默认双方维护关系.(mappedBy 单向关系不需要设置该属性, 双向关系必须设置, 避免双方都建立外键字段)

如果不在@OneToMany里加入mappedBy属性(相当于inverse=“true”)会导致自动生成一个多余的中间表;

  1. 一旦指定该属性, 则表明当前实体不能控制关联关系,
  2. 当前实体放弃控制关联关系之后, 不允许使用 @JoinColumn @JoinTable 修饰代表关联实体的属性

@OneToMany(targetEntity = Student.class,cascade=CascadeType.ALL,fetch=FetchType.LAZY,mappedBy = "room") @JoinColumn(name="r_id") 指定关联列

简而言之, 管理关联关系, 该属性标识对方属性(列)记录关联, 由对方负责关联关系 只有OneToOne,OneToMany,ManyToMany上才有mappedBy属性, ManyToOne不存在该属性(一方 无法管理 多方)

cascade 属性

cascade用于指示级联关系, 即两个实体间存在级联关系(一个类是另一个类中的属性)时, 当保存, 更新或删除一个实体时, 是否对关联的实体做出相应操作(数据库操作)

简而言之, 本实体修改时, 级联对方的操作

有中间表的级联删除 orphanRemoval

 
@Table(name="test_book")
public class TestBook implements Serializable {
 
	@OneToMany(cascade = CascadeType.ALL, orphanRemoval =true)
	@JoinTable(name = "test_book_addfile", 
		joinColumns = {@JoinColumn(name = "test_book_id",referencedColumnName = "id", columnDefinition ="char(32)")},
		inverseJoinColumns = {@JoinColumn(name = "upload_resources_id",referencedColumnName = "id",columnDefinition ="char(32)")})
	private List<UploadResources> addFile;
	...
 
}
 
...
 
@Entity
@Table(name="upload_resources")
public class UploadResources implements Serializable {
..

当没有设置时, delete主对象时, 子对象只是去掉关系; remove子对象时也只是去掉关系, 如果增加orphanRemoval = true则会删除remove的子对象数据

上例 没有中间表时 级联删除没有什么问题, 当有中间表时entityManager.detach(UploadResources); 只会删除中间表的数据(去掉关系), 不会删除upload_resources表数据, orphanRemoval = true, 可以解决之 注意, 更新不能使用自己new 的List(集合实例)对象, 必须使用从数据库获取的由Hibernate代理的List(集合实例)进行增删; 类似这种建议使用 Set 集合对接, 直接 Set.add 替换重复ID的对象

一对一的映射(one-to-one)

@Entity
@Table(name="user")
public class User implements Serializable {
	...
	@OneToOne
    @JoinColumn(name = "dept_id")
    private Dept dept;
}
 
 

双向

@Entity
@Table(name="dept")
public class Dept implements Serializable {
 
	@OneToOne(mappedBy="user")
    private User user;
}
 

多对多的映射(many-to-many)

必须有一个中间表了

@Entity
@Table(name="user")
public class User implements Serializable {
	...
	@ManyToMany
    @JoinTable(name = "users_roles", 
		joinColumns = {@JoinColumn(name = "user_id",referencedColumnName = "id")}, 
		inverseJoinColumns = {@JoinColumn(name = "role_id",referencedColumnName = "id")})
    private Set<Role> roles;
}
 
 

双向

@Entity
@Table(name = "role")
public class Role implements Serializable {
	...
 
	@ManyToMany(mappedBy = "roles")
    private Set<User> users;
 
}
 

忽略脏数据 @NotFound

不严谨的项目总是有些脏数据! 关联的实体找不到而触发 javax.persistence.EntityNotFoundException: Unable to find xxxxx with id 异常;

**optional属性 当 optional 为 false 时 关联实体一旦为 null 则整个查询结果为 null,生成的查询语句为 inner join 当 optional 为 true 时 即使关联实体为 null 仍能获取查询结果,只有关联属性为 null,生成的查询语句为 left join

hibernate 的 org.hibernate.annotations.NotFound注解可以配置NotFoundAction .IGNORE 忽略此类异常.

@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name="JY_BASE_ANIMALTYPE_ID",referencedColumnName = "ID")
@NotFound(action= NotFoundAction.IGNORE)
private JyBaseAnimaltype jyBaseAnimaltype;
 

多个实体 对应同一张表 (@DiscriminatorColumn)

@Data
@Entity(name = "BaseMeta")
@Table(name = "metas")
@DiscriminatorColumn(name = "type", discriminatorType = DiscriminatorType.INTEGER,
    columnDefinition = "int default 0")
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class BaseMeta extends BaseEntity {
    
	private Long id;
 
    /**
     * meta key
     */
    @Column(name = "meta_key", nullable = false)
    private String key;
 
    /**
     * meta value
     */
    @Column(name = "meta_value", length = 1023, nullable = false)
    private String value;
}

根据@DiscriminatorColumn 定义的字段区分是哪个实体的, 可以统一扩展一些数据

 
@Entity(name = "PostMeta")
@DiscriminatorValue("0")
@EqualsAndHashCode(callSuper = true)
public class PostMeta extends BaseMeta {
}
 
 
@Entity(name = "SheetMeta")
@DiscriminatorValue("1")
@EqualsAndHashCode(callSuper = true)
public class SheetMeta extends BaseMeta {
 
 

Hibernate

MORE THAN AN ORM, DISCOVER THE HIBERNATE GALAXY.

hibernate orm 5.6 doc hibernate orm 6.1 doc hibernate orm 6.1 api doc

jakarta 9 api

生命周期回调与拦截器 (LifeCycle Validateable Interceptor)

实体类通过实现 Lifecycle 接口,即可在特定的持久化阶段,触发特定的处理过程

LifeCycle

在某些情况下,我们需要对实体对象的CRUD操作进行捕获并执行相应的处理。在数据库层,这通常通过触发器(Triger)实现。 Hibernate 通过Lifecycle、Validatable接口制定了实体对象CRUD过程中的回调(CallBack)方式 实体类通过实现 org.hibernate.classic.Lifecycle 接口,即可在特定的持久化阶段,触发特定的处理过程

https://docs.jboss.org/hibernate/orm/5.6/javadocs/org/hibernate/classic/Lifecycle.html

org.hibernate.classic.Lifecycle

onSave: called just before the object is saved onUpdate: called just before an object is updated, ie. when Session.update() is called onDelete: called just before an object is deleted onLoad: called just after an object is loaded

对于onSave、onUpdate、onDelete方法,如果返回 true 则意味着需要中止执行对应的操作过程。如果代码运行期间抛出了CallbackException,对应的操作也会被中止。

Validateable

Validatable接口定义了数据验证实现方式。实体类实现Validatable接口,并在validate对当前待保存的数据进行验证,以保证数据逻辑合法。

Validatable.validate方法将在实体被持久化之前得到调用以对数据进行验证。但注意,与Lifecycle的回调方法不同,此方法在实体对象的生命周期内可能被数次调用,因此,此方法应仅用于数据本身的逻辑校验,而不要试图在此实现业务逻辑的验证。

Interceptor

The org.hibernate.Interceptor interface provides callbacks from the session to the application, allowing the application to inspect and/or manipulate properties of a persistent object before it is saved, updated, deleted or loaded.

One possible use for this is to track auditing information. The following example shows an Interceptor implementation that automatically logs when an entity is updated.

14.1. Interceptors

Interceptor 接口定义了 Hibernate 中的通用拦截机制。Session 创建时即可以指定加载相应的 Interceptor,之后,此Session的持久化操作工作都将首先经由此拦截器捕获处理。

public interface Interceptor {
    //对象初始化之前加载,这里的entity处于刚被创建的状态(也就是说属性均未赋值)
    public boolean onLoad(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) throws CallbackException;
    //Session.flush方法进行脏数据检查时,如果发现PO状态改变,则调用此方法
    public boolean onFlushDirty(Object entity, Serializable id, Object[] currentState, Object[] previousState, String[] propertyNames,  Type[] types) throws CallbackException;
    //将在对象被保存之前调用,这提供了一个对待保存对象属性进行修改的机会
    public boolean onSave(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) throws CallbackException;
    //将在对象被删除之前调用
    public boolean onDelete(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) throws CallbackException;
    //Session执行flush方法之前被调用
    public void preFlush(Iterator entities) throws CallbackException;
    //Session执行flush方法之后被调用
    public void postFlush(Iterator entities) throws CallbackException;
    //Session.saveOrUpdate方法时,将调用此方法判断对象是否尚未保存
    public Boolean isUnsaved(Object entity);
    //Session.flush方法时,将调用此方法判断对象是否为脏数据,这提供了脏数据检查的另一个拦截式实现渠道
    public int[] findDirty(Object entity, Serializable id, Object[] currentState, Object[] previousState, String[] propertyNames, Type[] types);
    //用于创建实体对象时,如果返回null,则Hibernate将调用实体类的默认构造方法创建实体对象
    public Object instantiate(Class clazz, Serializable id) throws CallbackException;
}

You can either implement Interceptor directly or extend the org.hibernate.EmptyInterceptor base class.

结果转换

版本5.2后已弃用 (但是一直没有替换的实现方法, 预计Hibernate6以后会有其替换的实现 [https://stackoverflow.com/questions/38240015/how-to-use-setresulttransformer-after-hibernate-5-2])

转DTO

org.hibernate.query.Query query = entityManager.createNativeQuery(sql)
		.setParameter(1, id)
		.unwrap(org.hibernate.query.Query.class)
		.setResultTransformer(Transformers.aliasToBean(OrgDeviceInfoFlex.class));
OrgDeviceInfoFlex singleResult = (OrgDeviceInfoFlex)query.getSingleResult();

转Map

nativeQuery
  .unwrap(org.hibernate.query.Query.class)
  .setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP);

自定义转换

List<PersonAndCountryDTO> personAndAddressDTOs = entityManager
.createQuery(
    "select p, c.name " +
    "from Person p " +
    "join Country c on p.locale = c.locale " +
    "order by p.id")
.unwrap( org.hibernate.query.Query.class)
.setResultTransformer(
    new ResultTransformer() {
        @Override
        public Object transformTuple(
            Object[] tuple,
            String[] aliases) {
            return new PersonAndCountryDTO(
                (Person) tuple[0],
                (String) tuple[1]
           );
        }
 
        @Override
        public List transformList(List collection) {
            return collection;
        }
    }
)
.getResultList();

6.1 后替换方式

hql-api-result-transformers

15.3.6. Query result transformers A program may hook into the process of building the query results by providing a org.hibernate.transform.ResultListTransformer or org.hibernate.transform.TupleTransformer. Hibernate provides several some built-in implementations of these interfaces, for example: Example 531. Using a ResultListTransformer

Deprecated. Use setTupleTransformer(org.hibernate.query.TupleTransformer<T>) or setResultListTransformer(org.hibernate.query.ResultListTransformer<R>)

实现软删除

hibernate 的实现软删除还是很方便的,只需在定义实体的时候添加注解 @SQLDelete、@Where。

@SQLDelete 是指实体删除时,执行的SQL语句。 @Where 是指查询是附加的查询条件

@Where

@Where 注解可以给Entity 统一添加条件查询

@Entity
@Where(clause = "delete_time is null")
public class Product {
 
 
	//关联关系的 也可以
  	@Where(clause = "db_status = 1")
	@OneToMany
	private Set<Goods> goods;
...
}
 

@FilterDef

但是如果要根据业务可以动态设置是否过滤 “删除的” 记录, 而不是全局, 这里要用到hibernate提供另外的两个注解 @Filter@FilterDef

 
//Entity
@SQLDelete(sql = "UPDATE sys_user SET deleted=true WHERE id=?")
@Filter(name = "softDeleteFilter", condition = "deleted= :deleted")
@FilterDef(name = "softDeleteFilter",
        parameters = @ParamDef(name = "deleted", type = "boolean"),
        defaultCondition = "deleted <> true")
@Entity
public class SysUser extends BaseIntegerEntity {
...
}
 
 
 
// 过滤器已经定义好了,要起作用的话,需要手动启用下
// Service 
@Override
public List<SysUserDTO> getMaleUserList() {
	List<SysUser> users;
	try (Session session = entityManager.unwrap(Session.class)) {
		Filter softDeleteFilter = session.enableFilter("softDeleteFilter");
		softDeleteFilter.setParameter("deleted", true);
		users = repository.findMales();
		session.disableFilter("softDeleteFilter");
	}
	return toListDTO(users);
}
 
 

获取 实体元数据

获取所有注册的实体元数据,;

 
EntityManagerFactory sessionFactory = 	SpringContextHolder.getBean(EntityManagerFactory.class);
MetamodelImplementor  metamodel= (MetamodelImplementor) sessionFactory.getMetamodel();//具体实现
 
Set<EntityType<?>> entitySet = metamodel.getEntities();
for (EntityType<?> et: entitySet) {
	//所有注册的实体 class
	Class<?> entityClass = et.getJavaType();
	
	//根据实体Class 获取元模型
	ClassMetadata classMetadata = (ClassMetadata) metamodel.entityPersister(entityClass);
	AbstractEntityPersister persi =(AbstractEntityPersister)classMetadata;//具体的实现
 
	//数据库表名
	String metaTableName = classMetadata.getTableName();
	final String tableName = AbstractProcess.escapeSymbolIfNeed(metaTableName);
	
	// Map<String, Field> fieldMap =  ReflectUtil.getFieldMap(entityClass);
	//属性列名 (这里不包含无主键的)
	// String[] propertyNames = classMetadata.getPropertyNames();
	// String[][] colReader = classMetadata.getSubclassPropertyColumnReaderClosure();
	for (int i = 0; i < propertyNames.length; i++) {
		String propertyName = propertyNames[i];
		if(colReader[i].length > 0) {
			//数据库列名
			String colName = AbstractProcess.escapeSymbolIfNeed(colReader[i][0]);
		}
	}
 
	//所有属性定义
	Iterable<AttributeDefinition> it = classMetadata.getAttributes();
	it.forEach(attr->{
		final Type type = attr.getType();
		//属性名
		final String attrName =attr.getName();
		if(type.isEntityType()) {//该属性是否是实体类型
		}else {
		}
	});
 
 
}
 
 

事务问题

事务隔离级别

事务并发时存在的问题

  1. 脏读(Dirty Read) 脏数据所指的就是未提交的数据,而脏读是指在一个事务处理过程里读取了另一个未提交的事务中的数据。比如,一个事务正在对一条记录做修改,在这个事务完成并提交之前,这条数据是处于待定状态的(可能提交也可能回滚),这时,第二个事务来读取这条没有提交的数据,并据此做进一步的处理,就会产生未提交的数据依赖关系。这种现象被称为脏读。

  2. 不可重复读(Non-repeatable Read) 一个事务先后读取同一条记录,而事务在两次读取之间该数据被其它事务所修改,则两次读取的数据不同,我们称之为不可重复读。例如事务T1在读取某一数据,而事务T2立马修改了这个数据并且提交事务给数据库,事务T1再次读取该数据就得到了不同的结果,发送了不可重复读。

不可重复读和脏读的区别:脏读是某一事务读取了另一个事务未提交的脏数据,而不可重复读则是在同一事务内读取了前一事务提交的数据,即前一次读到的数据是另一个事务提交前,后一次读到的数据是提交后的。

  1. 幻读(Phantom Read) 一个事务按相同的查询条件重新读取以前检索过的数据,却发现其他事务插入了满足其查询条件的新数据,这种现象就称为幻读。幻读是指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,比如这种修改涉及到表中的“全部数据行”。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入“一行新数据”。那么,以后就会发生操作第一个事务的用户发现表中还存在没有修改的数据行,就好象发生了幻觉一样。

幻读和不可重复读都是读取了另一条已经提交的事务(这点就脏读不同),所不同的是不可重复读查询的都是同一个数据项,而幻读针对的是一批数据整体(比如数据的个数)。

在实际开发和使用中,其实幻读问题是可以接受也符合用户的心理预期,比如在淘宝双十一抢东西,点击“购买”,成功进入**“下单页面”,结果到“付款页面”**却提示已经被抢完,这是可以接受的。

四个隔离级别

  1. 读未提交(Read uncommitted) 所有事务都可以看到其他未提交事务的执行结果。本隔离级别是最低的隔离级别,虽然拥有超高的并发处理能力及很低的系统开销,但很少用于实际应用。因为采用这种隔离级别只能防止更新丢失问题(这个问题现代关系型数据库已经不会发生),不能解决脏读,不可重复读及幻读问题。

  2. 读已提交(Read committed) 这是大多数数据库系统的默认隔离级别(但不是MySQL默认的)。它满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变。这种隔离级别可以防止脏读问题,但会出现不可重复读及幻读问题。

  3. 可重复读(Repeatable read) 这是MySQL的默认事务隔离级别,它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。这种隔离级别可以防止除幻读外的其他问题。

  4. 串行化(Serializable) 这是最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读、第二类更新丢失问题。在这个级别,可以解决上面提到的所有并发问题,但可能导致大量的超时现象和锁竞争,通常数据库不会用这个隔离级别,我们需要其他的机制来解决这些问题:乐观锁和悲观锁。

手动管理事务 entityManager

EntityTransaction tran = entityManager.getTransaction();
try {
	tran.begin();
 
	//do something ...
	tran.commit();
} catch (Exception e) {
	tran.rollback();
	e.printStackTrace();
}

Spring 管理的 EntityManager 不允许手动处理事务

Not allowed to create transaction on shared EntityManager - use Spring transactions or EJB CMT instead 共享的 EntityManager 事务应由 Spring统一管理;

那就要手动管理怎么办?

  1. 解为 Hibernate 的实现 Session 处理
Session session = entityManager.unwrap(Session.class);
Transaction tran = session.beginTransaction();
 
//do something ...
tran.commit();
// tran.rollback();
 
  1. 改为注入 EntityManagerFactory, 用它来创建的 EntityManager
EntityManager entityManager = entityManagerFactory.createEntityManager();
// 依据产品编码
String deviceId = message.getDeviceId();
OrgDeviceInfo deviceInfo = (OrgDeviceInfo) entityManager.createQuery("from OrgDeviceInfo t where t.uniqueCode =?1")
		.setParameter(1, deviceId)
		.getSingleResult();
String productCode = deviceInfo.getCode();
Session session = entityManager.unwrap(Session.class);
Transaction transaction = session.beginTransaction();

踩坑

用完要记得 entityManager.close(); close !

OLD ------------------------------

hibernate ORM

Hibernate_1{
	1.前言 (ORM)
	2.第一个Hibernate的应用
	3.增删改查
	4.关于Hibernate的对象三种状态
	5.Hibernate核心接口和工作原理
}
Hibernate_2{
	1.junit测试环境
	2.配置文件解释(Person.hbm.xml/部分)
 
}
Hibernate_3{
	1.组件的映射
	2.动态组件
	3.数组/List/Set/Map(集合类型)的映射
}
Hibernate_4{
	关联关系的配置
	1.一对一外键双向关联
	2.一对一主键双向主键关联
	3.一对多双向关联
}
Hibernate_5{
	1.多对多的双向关联
}
Hibernate_6{
	1.单表继承关系配置
	2.具体表继承关系配置  ;父类,与子类,多个子类,的数据分开存储
	3.每个具体一张表继承关系配置 ;父类,与子类,多个子类,的数据都存储在不同的表内
}
Hibernate_7{
	1.HQL(面向对象的方式查询)
		1.使用HQL批量查询/list() 与iterate()获取数据的区别
		...
		3.分页查询的API
		
	2.配置语句的查询
	3.(QBC)样例查询
	4.(QBC)离线查询
	5.标准的SQL方式查询
}
Hibernate_8{
	1.数据抓取策略
	2.悲观锁与乐观锁
}
Hibernate_9{
	1.过滤器filter
		只要对某个持久化对象配置并在session中启用了过滤器,则针对该对象的所有createQuery() HQL语句进行过滤.
	2.拦截器(Interceptor)
		在执行某个动作之前或之后加一个自定义的业务操作,如删除/保存/更新,只需重写对应的方法
		(类似oracle的触发器)
}
Hibernate_10{
	1.再谈缓存
		1.一级缓存
		2.list,iterate数据获得方法的2种区别,对于session缓存
			; ; ; 
		4.使用原则
		
	2.二级缓存
		; ; ; 
		7.load,get,list,数据获取,对于SessionFactory(二级)缓存的支持 见:HibernateTest.java
	3.查询缓存
	4.hibernate优化建议
	5.hibernate不适合的应用场景
}
 
Hibernate+11{
	注解 
	
}
 
//////////////////////////////////////////////////////////////////////////////////////////////////////////
Hibernate_1{
	
1.前言
	ORM(Object Relation Mapping),即:对象/关系映射,用于解决当前编程面向对象与数据库面向关系的冲突.
它是一类框架的总称,它概述了这类框架的基本特征
 
ORM规范映射思想:
	一个表映射成一个类
	一行记录(一条数据)映射成一个对象
	一列(以字段)映射成对象的属性
 
目前ORM的产品非常多如:apache的OJB,oracle的Toplink ...
	
.Hibernate是一个ORM轻量级的O/R Mapping框架
(轻量级,简单的,方便的;如struts2只要有execute()方法的都可以作为action,不需要继承实现任何接口)
	
	Hibernate必须的jar包
	可在hibernate-release-?.?.?.Final\lib\required,找到,注意还需要数据库驱动包
	
	
	
2.第一个Hibernate的应用
 
	1.设计一个持久化类
 
	  持久化类是应用程序用来解决商业问题的类(比如, 在电子交易程序中的Customer和Order); 
	持久化类, 就如同它的名字暗示的, 是短暂存在的, 它的实例会被持久性保存于数据库中; 
 
	/*
	 * 设计持久化类
	 *  1.通常有一个持久化标识符(id)
	 *  2.持久化标识符建议使用封装类(基本类型有默认值)
	 *  3.持久化类通常手动写一个无参数的构造,反射操作
	 *  4.持久化类不能是final
	 *  5.持久化类如果使用集合类型,只能使用接口类型进行声明(List/Set/Map) 
	 *      如:List list = new ArrayList();
	 *  6.属性通常建议通过get和set方法
	 *  
	 */
	public class Person {
		private Integer id;
		private int age;
		private Date bir;
		private String name;
		
		public Person(){}
 
		public Person(String name, int age, Date bir) {
			super();
			this.name = name;
			this.age = age;
			this.bir = bir;
		}
 
 
		/**
		 * @return the age
		 */
		public int getAge() {
			return age;
		}
		
		...
	}
	2.配置映射(映射java类)文件 Person.hbm.xml	参考官方文档/API
			文件名一般与映射的java类名相同Person.hbm.xml,
		  注意:<!>
			1.和MySQL不同, oracle通过序列实现主键的自增.
			2.在oracle 首先创建sequence
					CREATE SEQUENCE SEQ_ID INCREMENT BY 1 START WITH 1 NOMINVALUE NOMAXVALUE NOCYCLE NOCACHE NOORDER;
			3.在你的*.hbm.xml中的配置
				<id name="id" type="java.lang.Integer">
				<!-- generator id生成策略,持久化类通常有一个持久化标识符(id)-->
					<generator class="sequence" >
						 //<param name="sequence">SEQ_ID</param>
					</generator>
				</id>
 
	3.配置映射(映射数据库)文件hibernate.cfg.xml,路径位于src(根目录下)
		参考官方文档
			project/etc/hibernate.properties
 
		<!--配置数据库链接  方言(dialect) 貌似可以不配置,对性能有影响?-->
		<property name="hibernate.dialect">org.hibernate.dialect.Oracle9Dialect</property>
		<property name="hibernate.connection.driver_class">oracle.jdbc.driver.OracleDriver</property>
		<property name="hibernate.connection.url">jdbc:oracle:thin:@192.168.40.141:1521:orcl</property>
		<property name="hibernate.connection.username">yang</property>
		<property name="hibernate.connection.password">pass</property>
 
		<!--自动更新,如果数据库没有该表会自动新建-->
		<property name="hibernate.hbm2ddl.auto">update</property>
		update: 表示自动根据model对象来更新表结构, 启动hibernate时会自动检查数据库, 如果缺少表, 
	则自动建表; 如果表里缺少列, 则自动添加列;  
	还有其他的参数: 
		create: 启动hibernate时, 自动删除原来的表, 新建所有的表, 所以每次启动后的以前数据都会丢失;  
		create-drop: 启动hibernate时, 自动创建表, 程序关闭时, 自动把相应的表都删除; 所以程序结束时, 
		表和数据也不会再存在;  
		
		
		<!--配置信息 映射(mapping)-->
		<mapping resource="com/yang/hibernate/Person.hbm.xml"/>
		
	4.测试
	/*
	 * 
	 * 第一个hibernate
	 * 测试
	 * 
	 */
	public static void main(String [] args){
		
		//1.获得hibernate.cfg.xml中的配置
		Configuration cfg = new Configuration();
		
		//2.获得连接工厂,一般情况整个程序只有一个{线程安全} 该在hibernate3使用,在4已弃用,见Test2.java
		SessionFactory sessionFactory = cfg.configure().buildSessionFactory();
		
		//3.打开(获得)会话 session是hibernate的核心API,增删改查都是通过它{非线程安全}
		Session session = sessionFactory.openSession();
		
		//4.开始事务(hibernate中 增删改需要事务)
		Transaction ts = session.beginTransaction();
		
		//5.新建一个持久化对象
		Person p = new Person("小哈",22,new java.util.Date());
		
		//6.持久化到数据库,(操作),它返回持久化标识符(ID)
		//Integer id = (Integer)session.save(p);
			//在hibernate4 推荐使用persist(),进行持久化操作,无返回值
			session.persist(p);
		
		
		//7.提交事务
		ts.commit();
		
		//8.关闭session
		session.close();
		
		
	}
	
3.增删改查
 
	1.查询(查询不需要事务)
	
	1.1关于load与get方式的查询区别 参见:Test_2.java
	/*
	 * 
	 * 关于load与get方式的查询区别
	 * 
	 * Load方式延迟查询,
	 *   执行load(Person.class, 3)返回的只是代理对象,
	 *   当需要真正用到属性是才会执行sql查询
	 *   如果对应记录不存在,抛出ObjectNotFoundException:异常
	 * 	 它可以在Person.hbm.xml中配置是否立即查询lazy="true"
	 * 
	 * 
	 * get方式立即查询,
	 *   执行get(Person.class, 3)立即执行sql查询,返回真实对象记录
	 *   如果对应记录不存在,返回null
	 * 
	 */		
	//注册的方式组建SessionFactory 4.3的方式(推荐)
		ServiceRegistry sr = new StandardServiceRegistryBuilder().applySettings(config.getProperties()).build();
		
		SessionFactory factory =  config.buildSessionFactory(sr);
		
		Session session = factory.openSession();
		
		Person p = (Person)session.load(Person.class, 8);
		//Person p = (Person)session.get(Person.class, 8);
 
 
	2.更新
 
	/*更新方式4
	 * session.saveOrUpdate();方法
	 * 
	 * 新增或者更新,它是根据id是否存在,执行新增或更新
	 * 
	 * 注意,如果给定id没有对应记录,抛出异常:org.hibernate.StaleStateException
	 * 
	 */
		 
		//注册的方式组建SessionFactory 4.3的方式(推荐)
		ServiceRegistry sr = new StandardServiceRegistryBuilder().applySettings(config.getProperties()).build();
		
		SessionFactory factory =  config.buildSessionFactory(sr);
		
		Session session = factory.openSession();
		
		Transaction ts = session.beginTransaction();
		
		Person p = new Person();
		//给ID
		p.setId(40);
	
		p.setName("修改88");
		
		session.saveOrUpdate(p);
		
		ts.commit();
		session.close();
		factory.close();
	/*
	 * 更新方式3
	 * 
	 * session.merge(); 方法
	 * 该方法返回object,该对象是与session关联(是持久化对象)
	 * 即:当关闭,更新,刷新 session最终会同步到数据库.
	 */
	 
	 /*
	 * 
	 * 更新方式1
	 * 
	 * Person p = (Person)session.get(Person.class, 4);//持久化获得对象
	 *  Person p 当对象与session关联时,期间对对象的操作
	 *  当关闭,更新,刷新 session最终会同步到数据库,
	 *  
	 *  关闭session之后该对象处于脱管状态
	 *  
	 */
	 
	/*
	 * 
	 * 更新方式2
	 * 
	 * 新建对象(该对象不是持久化对象,)
	 * 
	 * 设修改的id
	 * 
	 * 
	 * 
	 */
		参见:Test_3
 
	3.删除
		Configuration config = new Configuration().configure();
		//注册的方式组建SessionFactory 4.3的方式(推荐)
		ServiceRegistry sr = new StandardServiceRegistryBuilder().applySettings(config.getProperties()).build();
		
		SessionFactory factory =  config.buildSessionFactory(sr);
		
		Session session = factory.openSession();
		//开始事务
		Transaction ts = session.beginTransaction();
		
//		//删除持久化对象
//		Person p = (Person)session.get(Person.class, 4);
//		session.delete(p);
		
		//删除临时状态
		Person p = new Person();
		//给ID
		p.setId(50);
		
		//删除
		session.delete(p);
 
	参见:Test_3		
	
	
4.关于Hibernate的对象三种状态
		见图:Hibernate对象的三种状态.png
	 
	 (1) 瞬时状态: 
			由new操作符创建, 且尚未与Hibernate Session关联的对象被认定为瞬时(Transient)
			
	 (2) 持久状态: 
			持久(Persistent)是实例在数据库中有对应的记录, 并拥有一个持久化标识(identifier); 
		持久(Persistent)的实例可能是刚被保存的, 或刚被加载的, 无论哪一种, 按定义, 它存在于相关联的Session作用范围内; 
		
	 (3)脱管状态: 
			与持久(Persistent)状态对象关联的Session被关闭后, 对象就变为脱管(Detached)的; 
		对脱管(Detached)对象的引用依然有效, 对象可继续被修改; 脱管(Detached)对象如果重新关联到某个新的Session上, 
		会再次转变为持久(Transistent)的(在脱管Detached其间的改动将被持久化到数据库); 
	
5.Hibernate核心接口和工作原理
	@see http://blog.csdn.net/yangwenxue_admin/article/details/45769195
	
	1.Hibernate有五大核心接口, 分别是: 
		SessionFactory , Session , Transaction , Query , Configuration ; 
		这五个接口构成了hibernate运行的基本要素, 可以执行存取, 持久化, 事务管理等操作; 
		这五个接口可以位于系统的业务逻辑层和持久化层; 
 
		(1)SessionFactory: 这是Hibernate的关键对象, 它是单个数据库映射关系经过编译后的内存镜像, 
		它也是线程安全的; 它是生成Session的工厂, 本身要应用到ConnectionProvider, 
		//该对象可以在进程和集群的级别上, 为那些事务之间可以重用的数据提供可选的二级缓存; (重量级对象, 整个应用应保持单态)
		
		(2)Session: 它是应用程序和持久存储层之间交互操作的一个单线程对象; 它也是Hibernate持久化操作的关键对象, 
		所有的持久化对象必须在Session的管理下才能够进行持久化操作; 
		//此对象的生存周期很短, 其隐藏了JDBC连接, 也是Transaction 的工厂; 
		//Session对象有一个一级缓存, 执行Flush之前, 所有的持久化操作的数据都在缓存中Session对象处; 
		
 
		(3)事务(Transaction): 代表一次原子操作, 它具有数据库事务的概念; 
		但它通过抽象, 将应用程序从底层的具体的JDBC, JTA和CORBA事务中隔离开; 
		在某些情况下, 一个Session 之内可能包含多个Transaction对象; 
		//虽然事务操作是可选的, 但是所有的持久化操作都应该在事务管理下进行, 即使是只读操作; 
		
		(4)Query: Query负责执行各种数据库查询; 它可以使用HQL语言或SQL语句两种表达方式; 
		它的返回值一般是List; 需要自己转换; 
		//负责执行各种数据库查询
		
		(5)Configuration: 对象用于配置并根启动Hibernate; Hibernate应用
		通过Configuration实例来指定对象—关系映射文件的位置或者动态配置Hibernate的属性, 
		然后创建SessionFactory实例; 
		//创建SessionFactory实例
	
	
	2.Hibernate运行过程: 
	
		1.通过Configuration().configure();读取并解析hibernate.cfg.xml配置文件
			(简略作读取hibernate.cfg.xml 中的配置)
		2.通过config.buildSessionFactory();//创建SessionFactory
		
		3.sessionFactory.openSession();//打开Sesssion
		
		4.session.beginTransaction();//创建事务Transation
		
		5.persistent operate持久化操作 //一般指Save这个方法
		
		6.session.getTransaction().commit();//提交事务
 
		7.关闭Session
 
		8.关闭SesstionFactory
	
	
.struts 与Hibernate结合 3.4
	见图:struts 与Hibernate结合.png
.结合Hibernate客户如何存储student到数据库? 3.4
	见图:结合Hibernate客户如何存储student到数据库.png
}
Hibernate_2{
 
1.junit测试环境
 
	新建JUnit test Case
	它有一些测试方法 
	@Before 测试之前初始化数据
	@After 测试之后释放数据
	@Test  加入@Test开头的都可以作为测试方法
	//预计返回值true 实际返回值 b
	Assert.assertEquals(true, b);
	
参见: HibernateTest.java
	
 
2.配置文件解释(部分)	
	Person.hbm.xml(hibernate-mapping)
	参考:官方hibernate-mapping-?.?.dtd dtd文档
 
	<?xml version="1.0"?>
	<!DOCTYPE hibernate-mapping PUBLIC
			"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
			"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
	//<!-- 映射(java类) package指明包(这里指明比较简洁),当然class元素可以指明完整路径 -->
	//<!--当映射对象没有get和set方法时(不推荐) default-access 属性设置访问方式  -->
 
	<hibernate-mapping package="com.yang.hibernate" >
 
	
	/*<!-- name指定类名,table指定表名
		lazy指定session.load()加载时是否立即执行查询
		dynamic-insert 指定动态插入,没有赋值的属性不会插入值
		dynamic-update 动态更新,没有赋值的属性不会更新
		mutable 是否允许更新操作
		select-before-update 执行更新前,执行一次查询比较变化,确认是否需要更新,但是会多执行一次查询
		where 查询前自动加上自定where的语句,达到整体控制查询条件目的-->*/
		
		<class name="Person" table="T_PERSON" >
			<id name="id" type="java.lang.Integer">
			//<!-- generator id生成策略,持久化类通常有一个持久化标识符(id/主键)-->
			/*
				1.和MySQL不同, oracle通过序列实现主键的自增.
				2.在oracle 首先创建sequence
						CREATE SEQUENCE SEQ_ID INCREMENT BY 1 START WITH 1 NOMINVALUE NOMAXVALUE NOCYCLE NOCACHE NOORDER;
				3.在你的*.hbm.xml中的配置
					
			*/
				<generator class="sequence" >
					 <param name="sequence">seq_id</param>
				</generator>
			</id>
			
			//<!-- 以下指定其他字段 column指定数据库表的列名,不写默认为name的值-->
			//<!-- type 一般自动转换,也可以指定-->
			// not-null 是否允许为空
			//unique 是否唯一约束
			//unique-key 指定约束名
			//lazy 指定session.load()加载时是否立即执行查询
			//index 指定索引
			<property name="name" column="t_name"/>
			<property name="age" column="t_age"/>
			<property name="bir" column="t_bir"/>
			//<!-- formula 伪列数据库中不存在,值来源于formula对应的查询语句 -->
			
			<property name="count" formula="(select count(*) from t_person)"/>
		</class>
	</hibernate-mapping>
 
 
 
3 hibernate缓存 1.一级缓存,2.二级缓存,3.查询缓存
 
	1.一级缓存,
	  session一级缓存,一级缓存生命周期很短,与session生命周期一致,所以也叫session级缓存,或事务缓存
	  
	    Session具有一个缓存, 是一块内存空间, 在这个内存空间存放了相互关联的java对象, 
			这种位于Session缓存内的对象也被称为持久化对象, Session是负责根据持久化对象的状态变化来同步更新数据库的; 
			
      Session的缓存是内置的, 不能被卸除的, 也被称为Hibernate的第一级缓存; 在正常情况下一级缓存是由Hibernate自动维护的, 
			无需人工干预; 
 
		1.1当应用程序调用Session接口的save(), update(), saveOrUpdate时, 如果Session缓存中还不存在相应的对象, 
				Hibernate就会自动的把该对象加入到一级缓存中去; 
		1.2当调用Session接口的load(), get()以及Query查询接口的list(), iterator()方法时, 
				如果Session缓存中存在相应的对象, 则不需要到数据库中去检索; 
		1.3当调用Session的close()方法时, Session缓存就被清空; 
		
	  session缓存的好处
		1.减少对数据的访问,需要访问时,如果缓存中有时会从缓存中取数据(见:HibernateTest.testSession()),
				当对象与数据库的值相同并不会操作数据库,
		2.缓存中的数据与数据库中同步,会把对象的多次修改合并sql语句
		
		3.防止对象图死循环
	
 
	?在做批量操作是如何控制session缓存占用量?
		@Test
		public void testSession2(){
			
			Transaction ts = session.beginTransaction();
			for(int i=0; i<1000; i++){
				Person p = new Person("t00"+i,1+i,new Date());
				session.persist(p);
				if(i%10 == 0){
					session.flush();//该方法立即(强制)同步到数据库执行sql语句
					session.clear();//已经同步到数据库,清空session缓存,从而控制缓存占用				
					
				}
			}
			ts.commit();
		}
		
	
	2.二级缓存, 3.查询缓存
:hibernate_10/笔记.c
 
	
	
}
Hibernate_3{
	
1.组件的映射
  即是:对象中(人 普通组件/person.jav),有一个成员变量(电话 普通组件/phone.java),又是一个对象时如何映射配置?
  
  
  1.普通组件的应用
		1.1组件类 见:普通组件/Phone.java
			public class Phone {
				private String homePhine;
				private String mmPhone
			...
			}
	  
		1.2组件配置,见Person.hbm.xml
	
					<property name="bir" column="t_bir"/>
					<!-- formula 伪列数据库中不存在,值来源于formula对应的查询语句 -->
					<property name="count" formula="(select count(*) from t_person)"/>
					
					<!-- 
						配置组件映射
						component指定映射的组件类
						property指定组件类的字段(属性)参与映射
					 -->
					<component name="phone">
						<property name="homePhine" column="t_homePhine"/>
						<property name="mmPhone" column="t_mmPhone"/>
					</component>
				</class>
			</hibernate-mapping>
 
 
		1.3 测试 见:普通组件/HibernateTest.java
			public void test() {
				
				Person p = new Person("组件测试",22, new Date());
				//实例组件
				Phone phone = new 	Phone("0763-123456","080-123456");
				//关联组件
				p.setPhone(phone);
				//
				ts = session.beginTransaction();
				
				session.save(p);
				//提交
				ts.commit();
				
		}
			
 
  2.动态组件
		2.1组件类 见:动态组件/Person.java	
			
			public class Person {
				private int age;
 
				private Date bir;
 
				private Integer id;
 
				private String name;
				//动态组件
				//如果使用集合类型,只能使用接口类型进行声明(List/Set/Map) 
				private Map attribute = new HashMap();
			
			..
			}
			
		2.2组件配置
				<property name="bir" column="t_bir"/>
				<!-- formula 伪列数据库中不存在,值来源于formula对应的查询语句 -->
				<property name="count" formula="(select count(*) from t_person)"/>
				
				<!-- 
					配置动态组件映射
					name="attribute" 指定映射的动态组件类
					property指定组件类的字段(属性)参与映射
						name="key1" 对应map集合的key
						column 指定列名
						type 必须一致
				 -->
				<dynamic-component name="attribute">
					<property name="key1" column="t_key1" type="string"></property>
					<property name="key2" column="t_key2" type="integer"></property>
				</dynamic-component>
			
			
		2.3	测试 见:动态组件/HibernateTest.java
			/*
			 * 
			 * 测试动态组件方法
			 * 加入@Test开头的都可以作为测试方法
			 */
			@Test
			public void test() {
				
				Person p = new Person("动态测试",22, new Date());
				//获得动态组件,并设置数据
				Map map =p.getAttribute();
				map.put("key1", "hahaha");
				map.put("key2", 123);
				//
				ts = session.beginTransaction();
				session.save(p);
				//提交
				ts.commit();
 
				}
			
			
3.数组/List/Set/Map(集合类型)的映射
 
	集合类型的与动态组件不同的是,组件不同的对象,(如:电话)人的延伸,集合本质上属于类的属性
 
	1.新建(持久化)映射类 见:集合类型/Person.java
		注意:持久化类如果使用集合类型,只能使用接口类型进行声明(List/Set/Map)
		
		public class Person {
			/*
			 * 数组
			 * 
			 * 同构类型数据
			 * 通过下标访问数据
			 * (效率低 不能自增长)
			 */
			private String[] myarrays;
			
			/*
			 * List 本质是数组实现的
			 */
			private List mylist;
			
			/*
			 * Map 通过key 访问 value
			 * 
			 * 
			 */
			private Map mymap;
			/*
			 * Set 只有值本身
			 * 
			 */
			private Set myset;
		
		}
 
	2.配置
		见:集合类型/Person.hbm.xml
		值得注意的是,存储/List/Set/Map的类型,会建立另外的一张从表储存它们的值key,value,或者数组下标
		/*<!-- 
			数组的映射配置
			<key column="id" /> 指定外键列名 需要和(t_person)主表建立关联
			<list-index column="mark"/> 指定数组下标列名
			 -->*/
			<array name="myarrays">
				<key column="id" />
				<list-index column="mark"/>
				<element column="val" type="string"/>
			</array>
			/*<!-- 
				List的映射配置
				<key column="id" /> 指定外键列名  需要和(t_person)主表建立关联
				<list-index column="mark"/> 指定下标列名
			 -->*/
			<list name="mylist">
				<key column="id" />
				<list-index column="mark"/>
				<element column="val" type="string"/>
			</list>
			/*<!-- 
				mpa的映射配置
				
				<key column="id" /> 指定外键列名  需要和(t_person)主表建立关联
				<map-key column="map-kay" type="string"/> 指定Map的key 列名必须指定类型
				<element column="val" type="string"/> 指定Map的value列名
			
			 -->*/
			 <map name="mymap">
				<key column="id" />
				<map-key column="map-kay" type="string"/>
				<element column="val" type="string"/>
			 </map>
			 /*<!-- 
				set的映射配置
				<key column="id" />指定外键列名 需要和(t_person)主表建立关联
				<element column="val" type="string"/> 指定set的value列名
			 -->*/
			 <set name="myset">
				<key column="id" />
				<element column="val" type="string"/>
			 </set>		
		</class>
	</hibernate-mapping>
	
	3.测试 
	见:集合类型/HibernateTest.java
		/*
		 * 
		 * 测试数组,list,map,set的映射
		 * 
		 * 加入@Test开头的都可以作为测试方法
		 */
		@Test
		public void test() {
			
			Person p = new Person("测试00",22, new Date());
			//
			String [] arrays = {"aa","bb"};
			p.setMyarrays(arrays);
			//
			List list = new ArrayList();
			list.add("list-val001");
			list.add("list-val002");
			p.setMylist(list);
			//
			Map map = new HashMap();
			map.put("mapK001", "德玛西亚");
			map.put("mapK002", "暗影岛");
			p.setMymap(map);
			//
			Set set = new HashSet();
			set.add("set001");
			set.add("set002");
			p.setMyset(set);
			//
			ts = session.beginTransaction();
			session.save(p);
			//提交
			ts.commit();
 
			}
			
	
	
}
Hibernate_4{
	关联关系的配置
	
	双向关联,即是可以双向查找访问
	
	单向关联,即是只能一方查找访问
	
	address自增序列
	CREATE SEQUENCE address_seq
		INCREMENT BY 1 -- 每次加几个
		START WITH 1 -- 从1开始计数
		NOMAXvalue -- 不设置最大值
		NOCYCLE -- 一直累加, 不循环
		CACHE 10; --设置缓存cache个序列,如果系统down掉了或者其它情况将会导致序列不连续, 也可以设置为-NOCACHE
		
	1.一对一外键双向关联
	
	
		1.设计person(持久化)类
			public class Person {
			private int age;
			private Date bir;
			private Integer id;
			private String name;
			private int count;
			
			private Address address;
			
			public int getCount() {
				return count;
			}
			...
			}
		2.设计address(持久化)类
			public class Address {
	
			private Integer id;
			private String descs;
			private int code;
			private Person person;
			
			public Integer getId() {
				return id;
			}
 
			public void setId(Integer id) {
				this.id = id;
			}
			...
			}
		3.配置
			<!-- 以下指定其他字段 column指定数据库表的列名,不写默认为name的值-->
			<property name="name" column="t_name"/>
			<property name="age" column="t_age"/>
			<property name="bir" column="t_bir"/>
			<!-- formula 伪列数据库中不存在,值来源于formula对应的查询语句 -->
			<property name="count" formula="(select count(*) from t_person)"/>
			/*<!-- 以person为主表,配置address 
				cascade 指定级联all操作,自动保存从表
			注意Address区分大小写-->*/
			
			<one-to-one name="Address" cascade="all"/>
		</class>
		
		
		<!-- 合并在一起方便比对 -->
		<class name="Address" table="T_ADDRESS" >
			<id name="id" type="java.lang.Integer">
				<generator class="sequence" >
					 <param name="sequence">address_seq</param>
				</generator>
			</id>
			<property name="descs" />
			<property name="code" />
			
			/*<!-- 配置与person关联,
			本质many-to-one是多对一,
			但是添加unique约束,不为空,约束上是一对一
			constraint_person_id 添加列名更加直观
			-->*/
			<many-to-one name="Person" column="constraint_person_id" unique="true" not-null="true" />
		</class>
	</hibernate-mapping>
 
 
 
		4.测试
		/*
		 * 
		 * 测试一对一双向关联
		 * 
		 * 加入@Test开头的都可以作为测试方法
		 */
		@Test
		public void test() {
			//新建person
			Person p = new Person("测试add",22, new Date());
			//新建Address
			Address add = new Address("广州",1100);
			//相互设置/关联
			p.setAddress(add);
			add.setPerson(p);
			//保存
			ts = session.beginTransaction();
			
			//session.save(add); 注意保存主表->从表顺序,因为配置关联,保存先后的关系,hibernate会自动发送updata更新从表外键
			//cascade 指定级联操作,自动保存从表
			session.save(p);
			session.save(add);
			//提交
			ts.commit();
 
		}
		
		/*
		 * 
		 * 测试一对一
		 * 双向关联,
		 * 如何相互查询访问对方?
		 * 
		 * 加入@Test开头的都可以作为测试方法
		 */
		@Test
		public void test2() {
			/* 通过 主表找从表
				Person person = (Person)session.get(Person.class, 18);
				System.out.println("person="+person);
				System.out.println("--------------");
				System.out.println("add="+person.getAddress());
			 */
			
			/* 通过 从表找主表
				Address add = (Address)session.get(Address.class, 18);
				System.out.println("add="+add);
				System.out.println("--------------");
				System.out.println("person="+add.getPerson());
			*/
			
			
		}
	
 
	2.一对一主键双向关联
		1.相对于只有Address的配置需要更改
	
		<class name="Address" table="T_ADDRESS" >
		//<!-- 主键一对一,主键不能自己生成,ID生成策略于主表上 -->
			<id name="id" type="java.lang.Integer">
				<generator class="foreign" >
					 <param name="property">Person</param>
				</generator>
			</id>
			<property name="descs" />
			<property name="code" />
			
			/*<!-- 
				基于主键一对一
				从表使用 one-to-one 
				name 指定 address类中 Person的应用变量名
				constrained 启用约束控制(一对一)?
			-->*/
			<one-to-one name="Person" constrained="true"/>
		</class>
 
		2.测试代码都不用改
		/*
		 * 
		 * 测试一对一双向关联
		 * 
		 * 加入@Test开头的都可以作为测试方法
		 */
		@Test
		public void test() {
			//新建person
			Person p = new Person("测试add",22, new Date());
			//新建Address
			Address add = new Address("广州",1100);
			//相互设置/关联
			p.setAddress(add);
			add.setPerson(p);
			//保存
			ts = session.beginTransaction();
			
			//session.save(add); 注意保存主表->从表顺序,因为配置关联,保存先后的关系,hibernate会自动发送updata更新从表外键
			session.save(p);
			session.save(add);
			//提交
			ts.commit();
 
			}
 
 
		3.查询访问对方
		/*
		 * 
		 * 测试一对一
		 * 双向关联,
		 * 如何相互查询访问对方?
		 * 
		 * 加入@Test开头的都可以作为测试方法
		 */
		@Test
		public void test2() {
			/* 通过 主表找从表
				Person person = (Person)session.get(Person.class, 18);
				System.out.println("person="+person);
				System.out.println("--------------");
				System.out.println("add="+person.getAddress());
			 */
			
			/* 通过 从表找主表(它会延迟查询主表)
				Address add = (Address)session.get(Address.class, 18);
				System.out.println("add="+add);
				System.out.println("--------------");
				System.out.println("person="+add.getPerson());
			*/
			
			
		}
 
 
	3.一对多双向关联
		使用集合来保存多个映射
		
		1.Person
			public class Person {
				private int age;
				private Date bir;
				private Integer id;
				private String name;
				private int count;
				//一对多,存储多个地址的引用
				private Set<Address> addressSet;
			}
		2.配置 Person类hibernate-mapping
		
			<property name="count" formula="(select count(*) from t_person)"/>
			// <!-- set 存储多个地址 
				// name="addressSet" Person的集合引用变量名
				// cascade="all"  级联all操作
				// inverse="true" 把
			// -->
			<set name="addressSet" cascade="all" inverse="true">
				<key column="p_id" />
				<one-to-many class="Address"/>
			</set>
			</class>
		
			2.1.如果需要顺序使用list,只需要指定下标的列名,类型
			
					注意 :inverse 就不能为true了
				<list name="addressSet" cascade="all" inverse="false">
					<key column="p_id" />
					<index column="顺序" type="integer"?
					<one-to-many class="Address"/>
				</list>
			
			2.2.使用map只需要指定map的key列名,类型
			
					注意 :inverse 就不能为true了
				<map name="addressSet" cascade="all" inverse="true">
					<key column="p_id" />
					<index column="map-key" type="string"?
					<one-to-many class="Address"/>
				</map>
			
		3.配置 Address类hibernate-mapping
			// <!-- 一对多,从表没有任何约束 ,
			// 注意 如果主表上配有column则这个值必须一致-->
		<many-to-one name="person" column="p_id" />
 
		4.测试
			/*
			 * 
			 * 
			 * 测试一对多双向主键关联
			 * 
			 * 如何查找访问对方
			 * 
			 */
			@Test
			
			public void test5() {
				
				//通过主表查询从表
				Person p = (Person)session.get(Person.class, 53);
				
				Set<Address> hs = p.getAddressSet();
				Iterator<Address>  it= hs.iterator();
				System.out.println("Person="+p);
				while(it.hasNext())
				{
					
					Address add = it.next();
					System.out.println("Address="+add);
					
				}
				//通过从表查询主表
				Address add = (Address)session.get(Address.class, 58);
				System.out.println("add="+add);
				Person p = add.getPerson();
				System.out.println("Person="+p.getName());
			}	
	
}
Hibernate_5{
	
1.多对多的双向关联
	需要一张中间表
	关于见:多对多关系图.PNG
	
	1.新建持久化Person类
		public class Person {
			private int age;
			private Date bir;
			private Integer id;
			private String name;
			private int count;
			
			//多对多,存储多个地址的引用
			private Set<Address> addressSet;
			...
		}
	2.新建持久化Address类
		public class Address {
	
			private Integer id;
			private String descs;
			private int code;
			
			//多对多 用Set存储多个人
			private Set<Person> personSet;
			...
		}
	
	
	3.配置
				
		<!-- 
			//多对多
		 -->
		<property name="count" formula="(select count(*) from t_person)"/>
		<set name="addressSet" cascade="all" inverse="true" table="person_join_address">
			<key column="p_id" />
			<many-to-many class="Address" column="a_id" />
		</set>
		</class>
		
	<!-- =======================合并在一起方便比对=======================-->
	<class name="Address" table="T_ADDRESS" >
		<id name="id" type="java.lang.Integer">
			<generator class="sequence" >
				 <param name="sequence">address_seq</param>
			</generator>
		</id>
		<property name="descs" />
		<property name="code" />
		<!-- 
		//多对多
		 -->
		<set name="personSet" table="person_join_address">
			<key column="a_id" />
			<many-to-many class="Person" column="p_id" />
		</set>
		
 
	</class>
	
 
		见图:配置.PNG
		
		
	4.测试
			/*
			 * 
			 * 测试多对多双向主键关联
			 * 
			 * 如何相互查询访问对方?
			 * 
			 */
			@Test
			public void test5() {
				
				//通过主表查询从表
				Person p = (Person)session.get(Person.class, 54);
				
				Set<Address> hs = p.getAddressSet();
				Iterator<Address>  it= hs.iterator();
				System.out.println("Person="+p);
				while(it.hasNext())
				{
					
					Address add = it.next();
					System.out.println("Address="+add);
					
				}
				//通过从表查询主表
				Address add = (Address)session.get(Address.class, 61);
				
				Set<Person> hs = add.getPersonSet();
				Iterator<Person>  it= hs.iterator();
				
				System.out.println("Address="+add);
				while(it.hasNext())
				{
					
					Person per = it.next();
					System.out.println("Address="+per);
					
				}
				
				
			}	
}
Hibernate_6{
1.单表继承关系配置{
	
 
	父类,与子类,多个子类,的数据都存储在一张表内
	
	1.父类属性
		public class Person {
	
			private int age;
			private String name
		...
		}
	2.子类属性继承Person
		public class Student extends Person{
			
			private int num;
			private double score;
 
		...
		}
	3.配置
		<hibernate-mapping package="com.yang.hibernate" >
			<!-- 如果不是string,需要在class和subclass的discriminator-value=""指定辨别的值 -->
			<class name="Person" table="T_PERSON" discriminator-value="人类" >
				<id name="id" type="java.lang.Integer">
					<generator class="sequence" >
						 <param name="sequence">seq_id</param>
					</generator>
				</id>
				// <!-- 
					// 指定辨别列,辨别列必须在ID下面配置
					// type="string" 指定辨别列的数据类型(支持 string,int,char)
					// column="辨别列" 指定辨别列的列名
				 // -->
				<discriminator type="string" column="辨别列"></discriminator>
				
				<!-- 以下指定其他字段 column指定数据库表的列名,不写默认为name的值-->
				<property name="name" column="t_name"/>
				<property name="age" column="t_age"/>
				
				// <!-- 
					// 通过subclass配置子类
					// property 元素指定其他字段 column指定数据库表的列名,不写默认为name的值
					// 如果不是string,需要在class和subclass的discriminator-value=""指定辨别的值
				 // -->
				<subclass name="Student" discriminator-value="学生">
					<property name="num" column="t_num"/>
					<property name="score"/>
				</subclass>
			</class>
		</hibernate-mapping>
 
	4.测试
		/*
		 * 单表继承配置关系
		 * 
		 * select
		 */
		@Test
		public void testSelect() {
			
			//父类可以接受子类的类型(多态)
			System.out.println("------------<test>-------------");
			Person p = (Person)session.get(Person.class, 57);
			System.out.println("Person="+p);
		}
		
		
		/*
		 * 单表继承配置关系
		 * 
		 * 存储
		 */
	//	@Test
	//	public void testAdd() {
	//		System.out.println("------------<test>-------------");
	//		//新建person/Student
	//		Person p = new Person("小花",22);
	//		Student stu = new Student("小芳同学",22,1007,78.6);
	//		ts = session.beginTransaction();
	//		//保存
	//		//session.save(p);
	//		session.save(stu);
	//		ts.commit();
	//	}
	
	}
2.具体表继承关系配置{
	父类,与子类,多个子类,的数据分开存储
	
	父类:存储总数据
	子类:只存储扩展部分的数据
	(它们是一对一主键双向关联)
	
	与例上的区别仅仅是,配置上(不需要辨别列,通过joined-subclass配置关联,指定关联列名)
	<hibernate-mapping package="com.yang.hibernate" >
		
		<class name="Person" table="T_PERSON"  >
			<id name="id" type="java.lang.Integer">
				<generator class="sequence" >
					 <param name="sequence">seq_id</param>
				</generator>
			</id>
			<property name="name" column="t_name"/>
			<property name="age" column="t_age"/>
			/*
				通过joined-subclass关联
				<key column="p_id"/> 指定p_id列(为键)与父类关联
			*/
			<joined-subclass name="Student" >
				<key column="p_id"/>
				<property name="num" column="t_num"/>
				<property name="score"/>
			</joined-subclass>
		</class>
	</hibernate-mapping>
}	
3.每个具体一张表继承关系配置{
	父类,与子类,多个子类,的数据都存储在不同的表内
  单从表的角度看它们没有任何关系(也造成子类的数据与父类的数据重复)
  
	<hibernate-mapping package="com.yang.hibernate" 
	
		<class name="Person" table="T_PERSON"  >
		// 因为它们都是不同的独立表,(从表的角度看它们没有任何关系),需要更改id生成策略
			<id name="id" column="p_id">
			//hibernate(自动维护的) 高低算法
				<generator class="hilo" />
			</id>
			
			<property name="name" column="t_name"/>
			<property name="age" column="t_age"/>
			/*
				通过union-subclass关联
				<key column="p_id"/> 指定p_id列(为键)与父类关联
			*/
			<union-subclass name="Student" >
				<key column="p_id"/>
				<property name="num" column="t_num"/>
				<property name="score"/>
			</union-subclass>
		</class>
	</hibernate-mapping>
  

	
}
Hibernate_7{
1.HQL(面向对象的方式查询)
 
	HQL中只有属性和类
	表名(映射为类名),字段(映射为属性名),而不是使用实质在数据库中的表字段
	select name,password from users where id=20;
	
	1.使用HQL批量查询/list() 与iterate()获取数据的区别
	
	 * 测试 使用HQL批量查询
	 * HQL语句是hibernate推荐使用进行批量查询的语言
	 * 是完全面向对象的方式来进行查询开发
	public void test5() {
		//String sql="select * from T_PERSON";//改为
		String hql="from Person";
		Query query = session.createQuery(hql);
		/*
		 * list();数据获得方法1
		 * 
		 * 支持一级缓存的(写)不支持(读)
		 * 
		 * 它不支持读,即使一级缓存,有该数据,依然会发送sql语句查询
		 * 
		 * 写:如果一级缓存中有该对象则不会发送sql语句查询,直接在缓存中取
		 * 
		 * 
		 */
		List<Person> list = query.list();
		for(int i=0;i<list.size();i++){
			System.out.println("list.get(i)="+list.get(i));
		}
		System.out.println("<它不支持读,即使一级缓存有该数据时,依然会发送sql语句查询>");
		Query query2 = session.createQuery(hql);
		List<Person> list2 = query2.list();
		for(int i=0;i<list2.size();i++){
			System.out.println("list.get(i)="+list2.get(i));
		}
		
		//////////////////////////////////////////////////////////////
		/*
		 * iterate();数据获得方法2
		 * 
		 * N+1查询[先获得,所有符合条件的id]=1, [延迟查询,当需要数据时才发出sql语句查询获得]=N
		 * 
		 * 支持一级缓存的(读和写)
		 * 读:它会把所有结果缓存在一级缓存
		 * 写:如果一级缓存中有该对象则不会发送sql语句查询,直接在缓存中取
		 * 
		 */
		Person p = (Person)session.get(Person.class,70);
		System.out.println("p="+p);
		
		Iterator<Person> it= query.iterate();
		while(it.hasNext()){
			System.out.println("next="+it.next());
		}
		Person p2 = (Person)session.get(Person.class,70);
		System.out.println("p="+p2);
	}
	
	
	2.使用HQL其他字段查询
			/*
	 * 
	 * 使用HQL其他字段查询
	 */
	@Test
	public void test6() {
 
		/*
		 * 查询name 接受字符串
		 * 
		 */
		String hql="select name from Person";
		Query query = session.createQuery(hql);
		//获取name
		List<String> list = query.list();
 
		/*
		 * 查询age 接受数字类型
		 * 
		 */
		String hql="select age from Person";
		Query query = session.createQuery(hql);
		//获取age数字 用Integer
		List<Integer> list = query.list();
		
			
		/*
		 * 查询name ,age 接受多字段 
		 * 
		 */
		String hql="select name,age from Person";
		Query query = session.createQuery(hql);
		//获取name ,age 多字段 用object[] 接受
		//注意查询顺序
		List<Object[]> list = query.list();
		for(int i=0;i<list.size();i++){
			Object[] obj = list.get(i);
			System.out.println("下标0="+obj[0]+" 下标1="+obj[1]);
		}
		
		/*
		 * 可否解决object[] 接受的不方便?
		 * 
		 * 1.先person创建一个Person(name,age)的构造函数
		 * 2.hql使用增加new关键字查询
		 * 
		 */
		String hql="select new Person(name,age) from Person";
		Query query = session.createQuery(hql);
		List<Person> list = query.list();
		for(int i=0;i<list.size();i++){
			Person p = list.get(i);
			System.out.println("list.get(i)="+p);
		}
		
		/*
		 * 使用map来绑定接受结果
		 * 
		 * 1.先person创建一个Person(name,age)的构造函数
		 * 2.hql使用增加new 关键字查询 
		 * 3.默认是用数字(key)映射(查询到的值)value(注意,这里的数字(key)可以理解为下标)
		 * 4.便于理解可以在hql指定别名(Map会自动映射为key,默认是数字)
		 */
		//String hql="select new Map(name,age) from Person";
		String hql="select new Map(name as n,age as a) from Person";
		Query query = session.createQuery(hql);
		List<Map> list = query.list();
		
		for(int i=0;i<list.size();i++){
			Map m = list.get(i);
			Iterator it=m.entrySet().iterator();
			while(it.hasNext()){
				Map.Entry entry = (Entry) it.next();
				System.out.println(entry.getKey()+"---"+entry.getValue());
			}
		
		}
		
		
		/*
		 * 如何精确差查询,而不是查询集合?
		 * 
		 */
		String hql="from Person where id = 88";
		Query query = session.createQuery(hql);
		
		//如果已知只有一条记录,可以使用uniqueResult()获取,否则抛出异常
		Person p = (Person)query.uniqueResult();
		System.out.println("查询到的="+p);
 
	}
	
	
	3.分页查询的API
		/*
		 * 
		 * 调用hibernate的API分页查询
		 * hibernate的方言的配置其中之一的影响分页功能
		 * 
		 * 查询1到10条记录
		 */
		@Test
		public void test7() {
			String hql="from Person";
			Query query = session.createQuery(hql);
			query.setFirstResult(0);//注意是从0的下一个位置
			query.setMaxResults(10);
			List<Person> list = query.list();
			for(Person p: list){	
				System.out.println(p);
			}
			
		}
	
	
	4.防止SQL注入
		/*
		 * 
		 * 注意:以上的例子直接数据绑定,会导致数据注入(SQL注入)!!!
		 * 1.使用问号(?)占位符参数绑定方式解决
		 * 2.使用问号加数字的方式(?1),便于后期更改字段,但是定位是通过字符串定位的
		 * 3.使用冒号的方式(:),便于后期更改字段,但是定位是通过字符串定位的(hibernate 4.x版本以后的)
		 * 4.多参数的绑定查询
		 * 
		 * 
		 */
		@Test
		public void test8() {
		
		1.使用问号(?)占位符参数绑定方式解决
			
			String hql="from Person where id=? and name=?";
			Query query = session.createQuery(hql);
			//设置参数,注意不同于JDBC, 它是从零的位置开始!
			query.setInteger(0, 88);
			query.setString(1, "小28");
			Person p = (Person)query.uniqueResult();
			System.out.println("查询到的="+p);
			
		2.使用问号加数字的方式(?1),便于后期更改字段,但是定位是通过字符串定位的
			
			String hql="from Person where id=?2 and name=?1";
			Query query = session.createQuery(hql);
			//注意,使用字符串定位
			query.setInteger("2", 88);
			query.setString("1", "小28");
			Person p = (Person)query.uniqueResult();
			System.out.println("查询到的="+p);
			
			
		3.使用冒号的方式(:),便于后期更改字段,但是定位是通过字符串定位的
			
			String hql="from Person where id=:id and name=:name";
			Query query = session.createQuery(hql);
			//注意,使用字符串定位
			query.setInteger("id", 88);
			query.setString("name", "小28");
			Person p = (Person)query.uniqueResult();
			System.out.println("查询到的="+p);
			/*
			 * 多参数的绑定查询
			 * 
			 */
			String hql="from Person where id in(:ids)";
			Query query = session.createQuery(hql);
			//注意,使用字符串定位,后面条件使用数组(也可以是List)
			query.setParameterList("ids", new Object[]{66,77,88});
			
			List<Person> list = query.list();
			for(Person p: list){	
				System.out.println(p);
			}
			
			
	5.连接查询
			/*
			 * SQL连接查询
			 * 标准的SQL select * from T_PERSON p ,T_ADDRESS a where p.id=300  and p.id=a.p_id;
			 * 
			 * 将表转换成对象
			 * 将字段换成对象属性
			 * from Person p ,Address a where p.id=300  and p.id=a.p_id
			 * 
			 * 依然需要注意,笛卡尔集
			 * 
			 * 自连接使用(inner join) from Person p inner join p.name p_name
			 * 左连接使用(left join)
			 * 
			 * 
			 */
			// 外连接查询
			String hql="select p,a from Person p ,Address a where p.id=300  and p.id=a.person";
			//对比标准的SQL select * from T_PERSON p ,T_ADDRESS a where p.id=300  and p.id=a.p_id;
			Query query = session.createQuery(hql);
			List<Object[]> list = query.list();
			
			for(Object[] p: list){	
				System.out.println(Arrays.toString(p));
			}
			
			//
			
			
		}
	
	6.删除语句
	/*
	 * 删除语句
	 * 
	 * 必须要注意一点,进行批量更新/删除(DML)语句不会影响缓存
	 * 
	 * executeUpdate()是跳过session,直接操作数据库,更改完建议清空session,session.clear();
	 * 
	 */
	@Test
	public void test10() {
		String hql="delete Person where id = 279";
		//对比sql delete t_person where id < 279;
		ts = session.beginTransaction();
		//executeUpdate()返回受影响的行数
		int count = session.createQuery(hql).executeUpdate();
		print("共删除了"+count+"行");
		ts.commit();
		
		/*
		 * 支持级联删除
		 */
		Person p =(Person) session.get(Person.class, 281);
		ts = session.beginTransaction();
		session.delete(p);
		print("删除成功");
		ts.commit();
			
 
	}
	7.更新语句
	/*
	 * 更新语句
	 */
	@Test
	public void test9() {
		String hql="update Person set name='德玛西亚' where id = 300";
		//对比sql update t_person set t_name="德玛西亚" where = id = 300;
		ts = session.beginTransaction();
		//返回受影响的行数
		int count = session.createQuery(hql).executeUpdate();
		print("共更改了"+count+"行");
		ts.commit();
		
	}
	
	8.插入语句
	//批量插入
	@Test
	public void test4() {
		//批量插入
		Set<Address> ssSet = new HashSet<Address>();
	
		ts = session.beginTransaction();
		
		for(int i = 0;i<15;i++){
			
			Person p = new Person("小"+i,1000+i, new Date());
			
			Address add1 = new Address("广州",1100);
			Address add2 = new Address("深圳",3200);
			Address add3 = new Address("东莞",4406);
			Address add4 = new Address("香港",5606);
			ssSet.add(add1);
			ssSet.add(add2);
			ssSet.add(add3);
			ssSet.add(add4);
			
			//地址与人关联
			add1.setPerson(p);
			add2.setPerson(p);
			add3.setPerson(p);
			add4.setPerson(p);
			//人地址与关联
			p.setAddressSet(ssSet);
			//
			session.persist(p);
			if(i%20 == 0){
				session.flush();
			}
			
		}
		print("插入完成");
		ts.commit();
 
	}
	
2.配置语句的查询
	
	1.配置
		...
		</class>
		<!-- 绑定(查询)语句,需要把它配置在 class元素外面,一般是独立的xml文件 -->
		<query name="myQuery1">from Person</query>
	</hibernate-mapping>
	
	独立文件的配置
: myHQL.xml
		还需要在hibernate.cfg.xml引入查询语句的文件配置
		<mapping resource="com/yang/hibernate/myHQL.xml"/>
	2.测试
	/*
	 * 配置语句的查询
	 * 
	 */
	@Test
	public void test11() {
		print("----Test------");
		//getNamedQuery("myQuery1");获得配置的查询语句
		Query query = session.getNamedQuery("myQuery1");
		List<Person> list = query.list();
		for(Person p: list){	
			System.out.println(p);
		}
	}
	
	
 
 
3.(QBC)样例查询
	(QBC)=使用Hibernate封装的API进行查询
	已有一个对象,数据不完整的,按照这个样例去查询,并自动填充不完整的部分数据
	按照已有的属性自动拼装成SQL条件去查询
	
	@Test
	public void test14() {
		//新建一个对象,数据不完整只有名字
		Person p = new Person();
		p.setName("德玛西亚");
		//创建criteria 
		Criteria criteria = session.createCriteria(Person.class);
		//给定样例
		criteria.add(Example.create(p));
		//返回已经填充完整的对象
		List<Person> list = criteria.list();
		for(Person pl: list){	
			System.out.println(pl);
		}
		
	}
	
4.(QBC)离线查询
	(QBC)=使用Hibernate封装的API进行查询
	以上例子均,需要先与session绑定语句,才可以调用方法查询,
  造成绑定期间资源浪费
	/*
	 * 
	 * 离线查询
	 * 可以绑定查询数据,和查询条件时不需要session
	 * 需要时,把session放入,查询
	 */
	
	@Test
	public void test15() {
		print("-----test15---");
		//指定查询对象
		DetachedCriteria dc = DetachedCriteria.forClass(Person.class);
		//绑定查询条件
		dc.add(Restrictions.eq("id", 300));
		//当需要时,把session放入,查询
		Criteria criteria  = dc.getExecutableCriteria(session);
		//获取列表
		List<Person> list = criteria.list();
		for(Person pl: list){	
			System.out.println(pl);
		}
		
	}
	
	4.1 QBC限定
		https://www.cnblogs.com/langtianya/p/4718216.html
	
	
	
	
	
5.标准的SQL方式查询
	/*
	 * 标准Sql语句执行
	 */
	@Test
	public void test16(){
		String sql="select * from t_person";
		SQLQuery query = session.createSQLQuery(sql);
		//注意它返回的是Object[]
		List<Object[]> list = query.list();
		for(Object[] pl: list){	
			System.out.println(Arrays.toString(pl));
		}
	}
	
	如何封装成持久化类?
	它也可以使用HQL参数绑定
	/*
	 * 
	 * 标准Sql语句执行
	 * 如何封装成持久化类?
	 * 它也可以使用HQL参数绑定
	 */
	@Test
	public void test17(){
		String sql="select * from t_person where id in(:ids)";
		SQLQuery query = session.createSQLQuery(sql);
		//绑定持久化类
		query.addEntity(Person.class);
		query.setParameterList("ids", new Object[]{280,290,300});
		List<Person> list = query.list();
		for(Person pl: list){	
			System.out.println(pl);
		}
	}
	
	配置SQL语句的查询
	1.配置
		配置文件基本一样,使用sql-query配置语句
		
		<!-- 标准的sql语句的配置 -->
		<sql-query name="mySQL"><![CDATA[
		select * from t_person 
		where id=:rid
		]]></sql-query>
 
	2.测试
	/*
	 * 配置Sql语句的查询
	 * 参数绑定
	 */
	@Test
	public void test18() {
		print("----Test------");
		SQLQuery query = (SQLQuery)session.getNamedQuery("mySQL");
		query.setParameter("rid", 300);
		//绑定持久化类
		query.addEntity(Person.class);
		
		List<Person> list = query.list();
		for(Person p: list){	
			System.out.println(p);
		}
	}
	
}
Hibernate_8{
1.数据抓取策略
 
		<!-- set 存储多个地址 
			name="addressSet" Person的集合引用变量名
			cascade="all"  级联all操作
			inverse="true" 将关联关系控制管理交个对方管理
			通常关联关系管理交给多的一方管理
			fetch 指定抓起策略
			通过(join),合并成一条语句,连接表查询
			1.fetch="join" 通过(join),合并成一条语句,连接表查询(发送一次查询语句,立即查询)
			2.fetch="select"[默认],延迟加载(本例发送二次查询语句,当需要address的数据时才发生sql语句查询)
			3.fetch="subselect" 延迟加载(本例发送二次查询语句,当需要address的数据时才发生sql语句查询)
			batch-size="3" 指定批量操作,每次获取3条为一批数据返回
		-->
		<set name="addressSet" cascade="all" inverse="true" fetch="join" batch-size="3">
			<key column="p_id" />
			<one-to-many class="Address"/>
		</set>
 
	</class>
	
 
	 * 默认情况下,延迟加载(当需要address的数据时才发生sql语句查询)
	 * 配置时 fetch 指定抓起策略
	@Test
	public void test2(){
		Person p = (Person)session.get(Person.class, 365);
		print(p);
		print("------------------------");
		print(p.getAddressSet());
		
		/*
		 * 指定批量抓取
		 * 	batch-size="3" 批量操作,每次获取3条为一批数据返回
		 * (每三条后,如果还需要数据,再发送一次SQL查询,获取三条)
		 */
		String hql="from Person";
		Query query = session.createQuery(hql);
		List<Person> list = query.list();
		for(Person p:list){
			print(p);
			print("------------------------");
			Set set = p.getAddressSet();
			Iterator it = set.iterator();
			while (it.hasNext()){
				print(it.next());
			}
			
		}
 
		
	}
 
2.悲观锁与乐观锁
	引用:http://blog.csdn.net/hongchangfirst/article/details/26004335
	http://blog.163.com/liuweiyoung@126/blog/static/1731310452013328114929783/
	1.乐观锁
		乐观锁(Optimistic Lock), 顾名思义, 就是很乐观, 每次去拿数据的时候都认为别人不会修改, 所以不会上锁, 
		但是在更新的时候会判断一下在此期间别人有没有去更新这个数据, 可以使用版本号等机制; 
		乐观锁适用于多读的应用类型, 这样可以提高吞吐量, 
		像数据库如果提供类似于write_condition机制的其实都是提供的乐观锁; 
	
	多数依靠程序逻辑上的实现
	
		
		
		1.hibernate 配置乐观锁
		
		为数据增加版本识别,在基于表的版本解决方案中,一般通过为表增加一个"version"字段来实现
 
		1.1配置
			<!-- 
			optimistic-lock="version" 指定数据表锁策略 为乐观锁策略
			-->
			<class name="Person" table="T_PERSON" optimistic-lock="version">
				<id name="id" type="java.lang.Integer">
					<generator class="sequence" >
						 <param name="sequence">seq_id</param>
					</generator>
				</id>
				<!-- 配置锁策略中的版本(version)的字段/属性 -->
				<version name="version" column="t_version" type="long"></version>
				...
		1.2测试
			/*
			 * 
			 * 测试optimistic-lock="version" 的乐观锁
			 * 
			 * 
			 * 第二个事务 抛出 org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): 
			 * 
			 * verion 由hibernate自动管理
			 * hibernate 提交事务时会自动将verion字段加1,防止数据不同步
			 * 
			 * 
			 */
			@Test
			public void test3(){
				Session tsess = factory.openSession();
				
				Person p = (Person)session.get(Person.class, 375);
				Person p2 = (Person)tsess.get(Person.class, 375);
				print("-----1-(start)----");
				
				ts = session.beginTransaction();
				p.setName("德玛西亚1-11");
				ts.commit();
				print("-----2-(end)----");
				
				//ts提交后 p2已经是脏数据了
				ts = tsess.beginTransaction();
				p2.setName("德玛西亚2-22");
				ts.commit();
				print("-----2-(end)----");
				
				
			}
	
	
	
	2.悲观锁
		悲观锁(Pessimistic Lock), 顾名思义, 就是很悲观, 每次去拿数据的时候都认为别人会修改, 所以每次在拿数据的时候都会上锁, 
		这样别人想拿这个数据就会block直到它拿到锁; 传统的关系型数据库里边就用到了很多这种锁机制, 
		比如行锁, 表锁等, 读锁, 写锁等, 都是在做操作之前先上锁; 
	
	多数依靠数据库的锁机制实现,以保证操作最大的独占性,但数据库的性能大量开销
  特别对长事务而言, 这样的开销往往无法承受
  
	2.1 hibernate自身不能实现悲观锁, 依赖于数据库; 
	
		只有数据库层提供的锁机制才能真正保证数据访问的排他性, 否则, 
	即使在本(hibernate)系统中实现了加锁机制, 也无法保证外部系统不会修改数据)
	
	select * from account wherename="Erica" for update  
	
	  这条 sql 语句锁定了 account 表中所有符合检索条件(name="Erica")的记录; 
	本次事务提交之前(事务提交时会释放事务过程中的锁), 外界无法修改这些记录; Hibernate 的悲观锁, 也是基于数据库的锁机制实现; 
		
	2.2 hibernate 悲观锁, 具体代码实现方式
	
		String hqlStr ="from TUser as user where user.name=‘Erica‘";  
		Query query = session.createQuery(hqlStr);  
		query.setLockMode("user",LockMode.UPGRADE); // 加锁  
		List userList = query.list();// 执行查询, 获取数据  
		
	query.setLockMode 对查询语句中, 特定别名所对应的记录进行加锁
	(我们为TUser 类指定了一个别名"user"), 这里也就是对返回的所有 user 记录进行加锁; 
	
}
Hibernate_9{
1.过滤器filter
	只要对某个持久化对象配置并在session中启用了过滤器,则针对该对象的所有createQuery() HQL语句进行过滤.
	
	1.1配置
		//<!-- 定义过滤器 ,指定参数-->
		<filter-def name="myFilter">
			<filter-param name="a" type="integer"/>
			<filter-param name="a2" type="integer"/>
		</filter-def>
		
		<class name="Person" table="T_PERSON" optimistic-lock="version">
			<id name="id" type="java.lang.Integer">
				<generator class="sequence" >
					 <param name="sequence">seq_id</param>
				</generator>
			</id>
			<!-- 配置锁策略中的版本(version)的字段/属性 -->
			<version name="version" column="t_version" type="long"></version>
			
			<!-- 以下指定其他字段 column指定数据库表的列名,不写默认为name的值-->
			<property name="name" column="t_name"/>
			<property name="age" column="t_age"/>
			//<!-- 使用指定的过滤器 -->
			<filter name="myFilter" condition="t_age between :a and :a2"></filter>
		</class>
	
	
	
	1.2测试 
		
		 * 
		 * 过滤器的使用
		 * 
		 * 注意:
		 * 
		 * 1.过滤器,必须启用才有效
		 * 2.过滤器只针对createQuery()即HQL; 对get 和 load 无效
		 * 3.过滤器(只要未关闭)的周期是整个session
		 
		@Test
		public void test1() {
			//启用过滤器,并且绑定参数,本例 只要session未关闭,对Person的所有操作都对增加between(该过滤器)
			Filter filter = session.enableFilter("myFilter");
			filter.setParameter("a", 1002);
			filter.setParameter("a2", 1003);
			//session.disableFilter("myFilter");//停用过滤器
			Query query = session.createQuery("from Person");
			List<Person> list = query.list();
			for(Person p: list){
				print(p);
			}
		}
		
		
		
		
		
2.拦截器(Interceptor)
	在执行某个动作之前或之后加一个自定义的业务操作,如删除/保存/更新
  只需重写对应的方法
	(类似oracle的触发器)
		
	1.MyInterceptor.java
	/*
	 * 
	 * 自定义拦截器
	 * 
	 * 在执行某个动作之前或之后加一个自定义的业务操作
	 * (类似oracle的触发器)
	 * 
	 * 需要实现org.hibernate包下的Interceptor 或者继承EmptyInterceptor
	 *  EmptyInterceptor是Interceptor空实现子类
	 *  
	 * 推荐继承EmptyInterceptor,如果实现Interceptor将需要重新所有操作的方法,
	 * 
	 * 
	 */
		public class MyInterceptor extends EmptyInterceptor{
			
			/*
			 * 保存操作时执行的方法
			 * 
			 * 它可以返回false阻止保存,但是session结束时自动提交了事务...汗
			 * 
			 * (non-Javadoc)
			 * @see org.hibernate.EmptyInterceptor#onSave(java.lang.Object, java.io.Serializable, java.lang.Object[], java.lang.String[], org.hibernate.type.Type[])
			 */
			@Override
			public boolean onSave(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) {
				// TODO Auto-generated method stub
				
				System.out.println("参数内容:");
				System.out.println("entity:"+entity);
				System.out.println("id:"+id);
				System.out.println("state:"+Arrays.toString(state));
				System.out.println("propertyNames:"+Arrays.toString(propertyNames));
				System.out.println("types:"+Arrays.toString(types));
				System.out.println("== 我就看看名字=.=");
				
				//只针对Person类型,判断下引用
				//if(entity instanceof Person){ 也可以
				if(entity.getClass() == Person.class){
					for(int i=0;i<propertyNames.length;i++){
						//名字字段
						if("name".equals( propertyNames[i])){
							
							if(  "拉登".equals(state[i]) ){
								System.out.println("名字有点不对劲");
								return false;
							}
						}
					}
					
					
				}else{
					System.out.println("嗯,名字没有敏感字");
					return super.onSave(entity, id, state, propertyNames, types);
				}
				return false;
			}
 
		}
 
 
		2.测试拦截器	
			/*
			 * 
			 * 测试拦截器
			 * 
			 */
			@Test
			public void test2() {
				//该方法是局部拦截器,只有对该session有效
				session = (Session) factory.withOptions().interceptor( new MyInterceptor()).openSession();
				
				//通过Configuration设置;该方法是全局拦截器
				//config.setInterceptor(new MyInterceptor());
				
				ts = session.beginTransaction();
				Person p = new Person("拉登",1009);
				session.persist(p);
				ts.commit();
 
			}
	
}
Hibernate_10{
 
 
1.再谈缓存
	 1.一级缓存,
		session一级缓存,一级缓存生命周期很短,与session生命周期一致,所以也叫session级缓存,或事务缓存,或局部缓存
		
		Session具有一个缓存, 是一块内存空间, 在这个内存空间存放了相互关联的java对象, 
			这种位于Session缓存内的对象也被称为持久化对象, Session是负责根据持久化对象的状态变化来同步更新数据库的; 
			
      Session的缓存是内置的, 不能被卸除的, 也被称为Hibernate的第一级缓存; 在正常情况下一级缓存是由Hibernate自动维护的, 
			无需人工干预; 
 
		1.1当应用程序调用Session接口的save(), update(), saveOrUpdate时, 如果Session缓存中还不存在相应的对象, 
				Hibernate就会自动的把该对象加入到一级缓存中去; 
		1.2当调用Session接口的load(), get()以及Query查询接口的list(), iterator()方法时, 
				如果Session缓存中存在相应的对象, 则不需要到数据库中去检索; 
		1.3当调用Session的close()方法时, Session缓存就被清空; 
		
	  session缓存的好处
		1.减少对数据的访问,需要访问时,如果缓存中有时会从缓存中取数据(见:HibernateTest.testSession()),
				当对象与数据库的值相同并不会操作数据库,
		2.缓存中的数据与数据库中同步,会把对象的多次修改合并sql语句
		
		3.防止对象图死循环
		
		见:hibernate_2/笔记.c
		
	<!重要>
	2.list,iterate数据获得方法的2种区别,对于session缓存
	
		2.1.Query.list()
		 * Query.list();数据获得方法1
		 * 
		 * 支持一级缓存的(写)不支持读
		 * 
		 * 它不支持读,即使一级缓存,有该数据,依然会发送sql语句查询
		 * 
		 * 写:如果一级缓存中有该对象则不会发送sql语句查询,直接在缓存中取
			String hql="from Person";
			Query query = session.createQuery(hql);
			List<Person> list = query.list();
		2.1.Query.iterate()
		 * Query.iterate();数据获得方法2
		 * 
		 * N+1查询[先获得,所有符合条件的id]=1, [延迟查询,当需要数据时才发出sql语句查询获得]=N
		 * 
		 * 支持一级缓存的(读和写)
		 * 读:它会把所有结果缓存在一级缓存
		 * 写:如果一级缓存中有该对象则不会发送sql语句查询,直接在缓存中取
		 * 
			String hql="from Person";
			Query query = session.createQuery(hql);
			Iterate<Person> iterate = query.iterate();
 
		见:hibernate_7/笔记.c
		
		
	3.一级缓存(session)的一些方法
		3.1 开始事务/当需要执行DML语句,必须需要事务的支持
			Transaction session.beginTransaction()
		
		3.2 判断某个对象是否存在session中	
			session.contains(object)
		
		3.3 强制刷新与数据库中的数据同步
			session.flush();
			
	4.使用原则
		1.尽量在晚的时候打开session,
			尽早关闭session,保证资源不浪费
	
		2.如果是在多线程的环境下开发,建议使用factory.getCurrentSession()的
			方式组建的线程安全的session, 还需要在总配置文件(hibernate.cfg.xml)
				<!-- 配置线程安全的session-->
				<property name="hibernate.current_session_context_class">thread</property>
			该方式组建的session必须开始事务(包括查询)
			该方式组建的session不能手动关闭(自动管理)
			见:
			
2.二级缓存,
	一级缓存基于session,二级缓存是基于SessionFactory(生命周期与它一致),
  一般来讲整个应用程序只有一个SessionFactory,在程序退出时才关闭它,这角度看
  它是全局的,可以在多个session中共享的
  
	1.hibernate 本身不提供二级缓存,需要配置第三方插件,一般情况下hibernate,
		发布包中lid\optional\ehcache中有可选的jar插件缓存依赖包(引入它们),还需要配置ehcache.xml文件,
		配置可以参考etc\ehcache.xml的文件
   
	2.ehcache.xml文件的配置,注意,它的存放路径跟hibernate.cfg.xml一样
		<ehcache>
			<!-- 当二级缓存超过maxElementsInMemor的个数,可以把缓存写入磁盘 -->
			<diskStore path="c:/cache_temp"/>
			<!--
				maxElementsInMemory="10000" -允许在二级缓存中持久化对象的数据量
				maxInMemory       - Sets the maximum number of objects that will be created in memory
				eternal          -  缓存中的对象是否允许销毁,即essionFactory被关闭后,是否还保留缓存在磁盘的文件
				timeToLiveSeconds -  缓存中对象的过期时间
				timeToIdleSeconds -  当持久化对象过期后,还可以存活多久
				overflowToDisk    - 是否允许缓存数据写入磁盘
				-->
 
			<defaultCache
				maxElementsInMemory="10000"
				eternal="false"
				timeToIdleSeconds="120"
				timeToLiveSeconds="120"
				overflowToDisk="true"
				/>
			<!-- 
			defaultCache 是默认的设置
		  
			当然你可以自定义
			 <cache name="sampleCache1"
				maxElementsInMemory="10000"
				eternal="false"
				timeToIdleSeconds="300"
				timeToLiveSeconds="600"
				overflowToDisk="true"
				/>
			 -->
		</ehcache>
 
	3.配置hibernate.cfg.xml的二级缓存
		默认情况下是开启二级缓存的,需要关闭
	  可以参考官方的:hibernate.properties.template 模板,Second-level Cache配置
	//<!-- 启用二级缓存,默认情况下是(true)开启二级缓存的,这条可以不配置 -->
	<property name="hibernate.cache.use_second_level_cache">true</property>
	//<!-- 配置使用那个二级缓存 -->
	<property name="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory.class</property>
	
	4.需要配置哪些持久化对象支持二级缓存
	  有两个地方可以配置
		1.在Person.hbm.xml文件可以配置
			...
			<class name="Person" table="T_PERSON" optimistic-lock="version">
				<!-- 配置该类支持二级缓存的读写 -->
				<cache usage="read-write"/>
			
			...
		2.在hibernate.cfg.xml文件可以配置
			//<!-- 配置该类支持二级缓存的读写 --> 需要在配置了该类映射之后
			<class-cache usage="read-write" class="com.yang.hibernate.Person"/>
	
	
	5.测试
		/*
		 * 测试二级缓存
		 * 
		 * 该例,如果没有开启二级缓存,因为session被关闭了,重新创建session,会发送两次SQL语句查询
		 * 
		 * 在配有二级缓存的的查询步骤
		 * 
		 * 搜索一级缓存->二级缓存->SQL查询->将数据写入缓存中
		 * 
		 * 二级缓存可以在多个session共享
		 * 它SessionFactory级别的
		 * 
		 * get 和 load 支持二级缓存的读写操作
		 * 
		 */
		@Test
		public void test1() {
			session = factory.openSession();
			Person p = (Person)session.get(Person.class, 390);
	//		Person p = (Person)session.load(Person.class, 390);
			print(p);
			if(session.isOpen()){
				session.close();
			}
			print("(如果没有开启二级缓存,session被关闭了,再次重新获取,会发送sql)");
			session = factory.openSession();
	//		Person p2 = (Person)session.load(Person.class, 390);
			Person p2 = (Person)session.get(Person.class, 390);
			print(p2);
			if(session.isOpen()){
				session.close();
			}
		}
	
	6.二级缓存的管理
		/*
		 * 测试二级缓存的管理
		 */
		@Test
		public void test4() {
			String hql="from Person";
			
			session = factory.openSession();
			
			Query query = session.createQuery(hql);
			List<Person> list = query.list();
			for(Person p: list){
				print(p);
			}
			session.close();
			//获得二级缓存的句柄
			Cache cache = factory.getCache();
			
			//判断该对象是否在二级缓存中,
			boolean b = cache.containsEntity(Person.class, 390);
			String isIn = b?"在":"不在";
			print("id=390的对象"+isIn+"二级缓存中");
			
			
			//1.移除某个缓存对象
			//cache.evictEntity(Person.class, 390);
			
			//2.移除某个类型的所有缓存对象
			cache.evictEntityRegion(Person.class);
			
			//3.移除除所有的缓存对象
			cache.evictAllRegions();
			
 
			//判断该对象是否在二级缓存中,
			boolean b2 = cache.containsEntity(Person.class, 390);
			String isIn2 = b2?"在":"不在";
			print("id=390的对象"+isIn2+"二级缓存中");
			
			
			
		}
	
 
	
  <!重要>
	7.load,get,list,数据获取,对于SessionFactory(二级)缓存的支持 见:HibernateTest.java
	
		1.读,在获取数据之前,它会先搜索二级缓存中,是否缓存了该对象,如果有则从缓存中取出数据,没有则发送SQL查询
		2.写,获取数据之后会,把该对象,放到二级缓存;
		
	  7.1. get 和 load 都支持二级缓存的读写操作
	
	  7.2. list支持二级缓存的写,不支持读
	
	  7.3 Iterator支持二级缓存的读和写操作
 
	8.适合加载在二级缓存的东西
		1.数据更新频率低
		2.允许偶尔出现并发问题的非重要数据
		3.常量数据
		4.不会被第三方修改的数据
	
	
	
	
3.查询缓存
	查询缓存是<!>依赖于二级缓存的,如果启用了查询缓存,当第一次执行查询语句时,hibernate或吧查询结果
  存放在二级缓存中,以后再次执行该查询语句时,只需从缓存中获得查询结果,从而提高查询性能.
  
  比较不好的是:只能对不变化的语句,或者不变的数据库表数据有效
  
 
	注意:
		list支持查询缓存
		Iterator不支持
 
	1.配置
 
	...
		//<!-- 启用二级缓存,默认情况下是(true)开启二级缓存的,这条可以不配置 -->
		<property name="hibernate.cache.use_second_level_cache">true</property>
		//<!-- 配置使用那个二级缓存 -->
		<property name="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</property>
		
		/*<!-- 启用查询缓存 认情况下是(false)
			查询缓存是基于二级缓存的
		-->*/
		<property name="hibernate.cache.use_query_cache">true</property>
		//<!--配置信息 映射(mapping)-->
		<mapping resource="com/yang/hibernate/Person.hbm.xml"/>
	...
 
	2.测试
		
		/*
		 * 测试查询缓存(list)
		 * Iterator不支持查询缓存
		 * 第二次查询,会搜索之前的查询缓存有没有相同的语句查询过,如果有会直接从二级缓存取出查询缓存的数据
		 * 
		 * 但是只能对不变化的语句,或者不变的数据库表数据有效
		 * 
		 * 
		 * 
		 */
		@Test
		public void test2() {
			
			String hql="from Person";
					
			session = factory.openSession();
			
			Query query = session.createQuery(hql);
			//需要 设置启用查询缓存
			query.setCacheable(true);
			List<Person> list = query.list();
			for(Person p: list){
				print(p);
			}
			
			session.close();
			
			print("(list本身不支持读,开启查询缓存,下例不会发送sql查询)");
			session = factory.openSession();
			
			Query query2 = session.createQuery(hql);
			//需要 设置启用查询缓存
			query2.setCacheable(true);
			
			List<Person> list2 = query2.list();
			for(Person p: list2){
				print(p);
			}
			
			session.close();
			
		}
 
 
 
4.hibernate应用场景
	见图:hibernate优化建议.jpg
 
 
5.hibernate不适合的应用场景
	1.对于一些关系模型设计不合理的老系统,也不能发挥hibernate优势
	2.数据量巨大,性能要求苛刻的系统,
 
 
}