package com.ilivoo.hibernate.example.unidirectiononetomany;

import java.util.Iterator;
import java.util.List;
import java.util.Set;

import org.hibernate.Criteria;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.hibernate.criterion.Restrictions;

public class PersonManager {
	public static void main(String[] args) {
		PersonManager manager = new PersonManager();
		//manager.savePerson();
		//manager.deletePerson();
		//manager.testSession();
		//manager.loadPerson();
		//manager.criteriaPerson();
		//manager.getPerson();
		//manager.loadPerson2();
		manager.listPerson();
	}
	
	/**
	 * 
	 */
	public void savePerson(){
		Session session = HibernateUtils.getSessionFactory().getCurrentSession();
		Transaction tx = null;
		try {
			tx = session.beginTransaction();
			Person per = new Person();
			per.setName("linchunfeng");
			Phone phone = new Phone();
			phone.setNumber(15986388210L);
			Phone phone2 = new Phone();
			phone2.setNumber(15986388211L);
			Phone phone3 = new Phone();
			phone3.setNumber(15986388212L);
			Phone phone4 = new Phone();
			phone4.setNumber(15986388213L);
			Phone phone5 = new Phone();
			phone5.setNumber(15986388214L);
			per.getPhones().add(phone);
			per.getPhones().add(phone2);
			per.getPhones().add(phone3);
			per.getPhones().add(phone4);
			per.getPhones().add(phone5);
			session.save(per);
			
			System.out.println("-------------------------------");
			/**
			 * 这里还是会发送六条语句， 其实这里就是非常影响性能的， 那么我们有没有更好的办法处理这个问题了， 最好就是不要设置cascade="all", 可以手动的进行添加操作。 
			 * 不过话又说回来， 我们应该尽量使用简单的方法去解决问题， 当且仅当我们发现了这里是性能瓶颈的时候， 我们才要试着去优化， 否则我们应该尽量不要这么去做。
			 */
			Phone phone6 = new Phone();
			phone6.setNumber(1232454543L);
			per.getPhones().add(phone6);
		    tx.commit();
		}
		catch (Exception e) {
		    if (tx!=null) 
		    	tx.rollback();
		    throw new RuntimeException(e);
		}
	}
	
	/**
	 * 
	 */
	public void deletePerson(){
		Session session = HibernateUtils.getSessionFactory().getCurrentSession();
		Transaction tx = null;
		try {
			tx = session.beginTransaction();
			Person per = (Person) session.get(Person.class, 1L);
			Set<Phone> phones = per.getPhones();
			for(Phone phone: phones){
				session.delete(phone);
			}
			phones.clear();
		    tx.commit();
		}
		catch (Exception e) {
		    if (tx!=null) 
		    	tx.rollback();
		    throw new RuntimeException(e);
		}
	}
	
	/**
	 * 
	 */
	public void testSession(){
		Person per = null;
		Session session = HibernateUtils.getSessionFactory().getCurrentSession();
		Transaction tx = null;
		try {
			tx = session.beginTransaction();
			per = (Person) session.get(Person.class, 1L);
			
		    tx.commit();
		}
		catch (Exception e) {
		    if (tx!=null) 
		    	tx.rollback();
		    throw new RuntimeException(e);
		}
		/**
		Phone phone = new Phone();
		phone.setNumber(12877678989L);
		per.getPhones().add(phone);
		此处设置的lazy="true", 不管无论如何， 在托管对象的时候， 都不能使用person的phones字段。
		*/
		Session session2 = HibernateUtils.getSessionFactory().getCurrentSession();
		Transaction tx2 = null;
		try {
			tx2 = session2.beginTransaction();
			/**
			Phone phone = new Phone();
			phone.setNumber(12877678989L);
			per.getPhones().add(phone);
			session2.update(per);
			可以看到， 如果lazy="true"这里还是会出错的， 因为当person变成了托管对象之后， hibernate根本就不会知道person内部是否发生了变化， 所以如果update的时候会发送phones的大小的插入语句， 这样就非常影响
			性能了， 所以我们一定要注意， 尽量不要让对象托管了之后， 再对对象的多的一方有太大的修改， 其实最好的办法就是， 我们不应该设置cascade="all"， 在多的这里， 我们应该手动的添加多的这一方， 这样就是非常好的做法了。
			 */
		    tx2.commit();
		}
		catch (Exception e) {
		    if (tx2!=null) 
		    	tx2.rollback();
		    throw new RuntimeException(e);
		}
	}
	
	/**
	 * 
	 */
	public void loadPerson(){
		Session session = HibernateUtils.getSessionFactory().getCurrentSession();
		Transaction tx = null;
		try {
			tx = session.beginTransaction();
			Person person = (Person) session.load(Person.class, 1L);
			Set<Phone> phones = person.getPhones();
			for(Phone phone: phones){
				System.out.println(phone.getNumber());
			}
		    tx.commit();
		}
		catch (Exception e) {
		    if (tx!=null) 
		    	tx.rollback();
		    throw new RuntimeException(e);
		}
	}
	
	/**
	 * 
	 */
	public void criteriaPerson(){
		Session session = HibernateUtils.getSessionFactory().getCurrentSession();
		Transaction tx = null;
		try {
			tx = session.beginTransaction();
			Criteria cirte = session.createCriteria(Person.class);
			cirte.add(Restrictions.eq("name", "fengxiang"));
			List list = cirte.list();
			Person per = (Person) list.get(0);
			System.out.println(per.getName());
		    tx.commit();
		}
		catch (Exception e) {
		    if (tx!=null) 
		    	tx.rollback();
		    throw new RuntimeException(e);
		}
	}
	
	/**
	 * 1. 这里我们需要注意了， 当我们的lazy使用在set上面的时候， 此时有三种加载模式， lazy = true、false、extra
	 * lazy="true", 表示加载person的时候不会立即加载所有的phone数据。但是当使用phone的时候会加载出所有的phone， 不管是任何的操作。
	 * lazy="false",表示加载person的时候， 会立即加载所有的phone数据。
	 * lazy="extra", 表示加载person的时候不会立即加载所有的phone数据， 但是当只是调用person.getPhones().size()的时候， 它和lazy="true"不一样， 它只会发生一条获取phone个数的语句。
	 * 2. 但是我们看很遗憾的看到默认情况下使用的是lazy="true"的， 不过可能是不同的版本不同的策略吧。
	 * 3. 一般情况下我们的集合的抓取模式lazy="extra"， 这是一个非常明智的选择。
	 * 4. 默认的抓取策略使用的是fetch="select"， 这是非常正确的选择。
	 * 5. 最大的原则就是， 我们必须使用的是lazy="true", 或者是extra， 直接加载是一个非常不明智的选择。所以fetch="select"也是一个无赖之举。
	 * 但也是字正确的选择。其实现在来说， select默认查询的时候也是使用了内连接策略， 并不会发生N+1条语句， 而只会1+1条语句。
	 * 
	 * 当lazy=true或者extra的时候， 都会返回代理类：org.hibernate.collections.PersistentSet的实例。
	 * 当lazy=true时： 返回的PersistentSet中的元素为空， 调用customer.getOders()并不能触发select，只有当调用其iterator、size、isEmpty、contains方法时才会执行数据库查询。
	 * 当lazy=extra时： 返回的PersistentSet中的元素也为空， 调用size、isEmpty、contains时也不能触发select，只有当调用iterator时才执行数据库查询。 
	 */
	public void getPerson(){
		Session session = HibernateUtils.getSessionFactory().getCurrentSession();
		Transaction tx = null;
		try {
			tx = session.beginTransaction();
			Person per = (Person) session.get(Person.class, 1L);
			//System.out.println(per.getPhones().size());
			Set<Phone> phones = per.getPhones();
			for(Phone phone: phones){
				System.out.println(phone.getNumber());
			}
		    tx.commit();
		}
		catch (Exception e) {
		    if (tx!=null) 
		    	tx.rollback();
		    throw new RuntimeException(e);
		}
	}
	
	/**
	 * 1. 可以看到， 当name的lazy="true"的时候， 不会loadperson的时候不会向数据库中发送任何的查询语句。 只有当我们
	 * 获取name的时候才发送查询语句。
	 * 2. 所以这里我们可以完全看出get和load的区别， 但是一般情况下， 我们使用get就可以满足了， 因为对于一对多和一对一
	 * 都是使用自己的抓取模式， 不会使用load的抓取模式。
	 * 3. 所以一般情况下只有当我们需要未抓取到数据就抛出异常， 或者是有property设置了lazy="true"的时候， 使用load方法才有意义的。
	 * 
	 */
	public void loadPerson2(){
		Session session = HibernateUtils.getSessionFactory().getCurrentSession();
		Transaction tx = null;
		try {
			tx = session.beginTransaction();
			Person per = (Person) session.load(Person.class, 1L);
			System.out.println(per.getId());
			//System.out.println(per.getName());
		    tx.commit();
		}
		catch (Exception e) {
		    if (tx!=null) 
		    	tx.rollback();
		    throw new RuntimeException(e);
		}
	}
	
	/**
	 * 1. 我们看到当使用Query查询出所有的person的时候， list方法和iterate方法就非常有区别， 当我们使用list的时候会忽略Person身上对应的phones字段上面的
	 * 配置的fetch="join", 其实所有的配置多会取消， 使用默认配置, 使得person身上的phones使用默认的抓取策略， lazy="true", fetch="select"。这样抓取的时
	 * 就不会抓取到person身上的phones。但是我们如果在person的phones上面配置， batch-size="2"， 那么当我们通过Query的list方法查询出多个person的时候， 而对我们的person来说又要使用它的的phones的时候，
	 * 此时我们就要注意了， 最好使用的是hibernate提供的批量抓取策略， 假如我们有10个person， 那么我们就不需要为每个person调用一次查询获得它的phones了， 而是按照批量抓取的方式抓取。
	 * 2. 这里还是要重新学习， batch-size， 和iterate以及list方法的使用。
	 */
	public void listPerson(){
		Session session = HibernateUtils.getSessionFactory().getCurrentSession();
		Transaction tx = null;
		try {
			tx = session.beginTransaction();
			Query query = session.createQuery("from Person");
			List<Person> list = query.list();
			for(Person person: list){
				Set<Phone> phones = person.getPhones();
				System.out.println(phones);
			}
		    tx.commit();
		}
		catch (Exception e) {
		    if (tx!=null) 
		    	tx.rollback();
		    throw new RuntimeException(e);
		}
	}
}
