04-Hibernater

hibernater的二级缓存

缓存

缓存(Cache): 计算机领域非常通用的概念。它介于应用程序和永久性数据存储源(如硬盘上的文件或者数据库)之间,其作用是降低应用程序直接读写硬盘(永久性数据存储源)的频率,从而提高应用的运行性能。缓存中的数据是数据存储源中数据的拷贝。缓存的物理介质通常是内存
缓存:程序<–(内存)–>硬盘

什么是二级缓存
  • hibernate 提供缓存机制:一级缓存、二级缓存
    • 一级缓存:session级别缓存,在一个session中共享数据。
    • 二级缓存:sessionFactory级别缓存,整个应用程序共享一个会话工厂,共享一个二级缓存。
  • SessionFactory的缓存两部分:
    • 内置缓存:使用一个Map,用于存放配置信息,预定义SQL语句等,提供给Hibernate框架自己使用,对外只读的。不能操作。
    • 外置缓存:使用另一个Map,用于存放用户自定义数据。默认不开启。外置缓存hibernate只提供规范(接口),需要第三方实现类。外置缓存有成为二级缓存。
二级缓存应用场景
  • 适合放入二级缓存中的数据:
    • 很少被修改
    • 经常被访问
    • 不是很重要的数据, 允许出现偶尔的并发问题
  • 不适合放入二级缓存中的数据:
    • 经常被修改
    • 财务数据, 绝对不允许出现并发问题
二级缓存的提供商
  • EHCache: 可作为进程(单机)范围内的缓存, 存放数据的物理介质可以是内存或硬盘, 对 Hibernate 的查询缓存提供了支持。–支持集群,redies
  • OpenSymphony `:可作为进程范围内的缓存, 存放数据的物理介质可以是内存或硬盘, 提供了丰富的缓存数据过期策略, 对 Hibernate 的查询缓存提供了支持
  • SwarmCache: 可作为集群范围内的缓存, 但不支持 Hibernate 的查询缓存
  • JBossCache:可作为集群范围内的缓存, 支持 Hibernate 的查询缓存

配置二级缓存

    <!-- 1.开启二级缓存 -->
	<property name="hibernate.cache.use_second_level_cache">true</property>
		
	<!-- 2.确定二级缓存的供应商 -->
	<property name="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</property>
	
	<!-- 映射文件 -->
		<mapping resource="com/tamakiakoo/entity/User.hbm.xml" />
		<mapping resource="com/tamakiakoo/entity/Address.hbm.xml" />
		
	<!-- 3.设置要缓存的对象 -->
	<!-- 
	    class的顺序是在映射文件之后的
		class:配置要缓存的对象.
		usage:缓存中的数据是否只读,一般都是read-only
	 -->
	<class-cache usage="read-only" class="com.tamakiakoo.entity.User"/>
	
    <!-- 缓存一个集合,把集合中装额对象也要缓存,不能只缓存集合,而不缓存集合里面的对象 -->
    <class-cache usage="read-only" class="com.tamakiakoo.entity.Address"/>
	<collection-cache usage="read-only" collection="com.tamakiakoo.entity.User.addresses"/>
<!-- class的顺序是在映射文件之后的-->
<class-cache usage="read-only" class="com.tamakiakoo.entity.User"/>

error:org.xml.sax.SAXParseException; lineNumber: 54; columnNumber: 20; 元素类型为 "session-factory" 的内容必须匹配
"(property*,mapping*,(class-cache|collection-cache)*,event*,listener*)"。

其中在hibernate.cfg.xml文件中配置二级缓存提供商

配置文件详解
defaultCache    
	    maxElementsInMemory="10000"    
	    maxElementsOnDisk="0"    
	    eternal="true"    
	    overflowToDisk="true"    
	    diskPersistent="false"    
	    timeToIdleSeconds="0"    
	    timeToLiveSeconds="0"    
	    diskSpoolBufferSizeMB="50"    
	    memoryStoreEvictionPolicy="LFU"/>
  • maxElementsInMemory(正整数): 在内存中缓存的最大对象数量
  • maxElementsOnDisk(正整数): 在磁盘上缓存的最大对象数量,默认值为0,表示不限制。
  • eternal: 设定缓存对象保存的永久属性,默认为 false 。当为 true 时 timeToIdleSeconds、timeToLiveSeconds 失效。
  • timeToIdleSeconds(单位:秒): 对象空闲时间,指对象在多长时间没有被访问就会失效。只对eternal为false的有效。默认值0,表示一直可以访问。
  • timeToLiveSeconds(单位:秒): 对象存活时间,指对象从创建到失效所需要的时间。只对eternal为false的有效。默认值0,表示一直可以访问。
  • overflowToDisk: 如果内存中数据超过内存限制,是否要缓存到磁盘上。
  • diskPersistent: 是否在磁盘上持久化。指重启jvm后,数据是否有效。默认为false。
  • diskSpoolBufferSizeMB(单位:MB): DiskStore使用的磁盘大小,默认值30MB。每个cache使用各自的DiskStore。
  • memoryStoreEvictionPolicy: 如果内存中数据超过内存限制,向磁盘缓存时的策略。默认值LRU,可选FIFO、LFU。
清空策略
  • FIFO(first in first out): 先进先出
  • LFU(Less Frequently Used): 最少被使用,缓存的元素有一个hit属性,hit值最小的将会被清除缓存。
  • LRU(Least Recently Used)默认策略: 最近最少使用,缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清除缓存。

Hibernate二级缓存的并发访问策略

Hibernate二级缓存的并发访问策略有四种:只读(read-only)、非严格读写(nonstrict-read-write)、读写(read-write)和事务(transactional)。但是目前还没有二级缓存提供者完全支持所有的并发访问策略。

方式说明
只读(read-only):对于永远不会被修改的数据可以采用这种并发访问策略,它的并发性能是最高的。但必须保证数据不会被修改,否则就会出错。
非严格读写(nonstrict-read-write):非严格读写不能保证缓存与数据库中数据的一致性,如果存在两个事务并发地访问缓存数据的可能,则应该为该数据配置一个很短的过期时间,以减少读脏数据的可能。对于极少被修改,并且可以容忍偶尔脏读的数据可以采用这种并发策略。
读写(read-write):读写策略提供了“read committed"数据库隔离级别。对于经常被读但很少修改的数据可以采用这种策略,它可以防止读脏数据。
事务(transactional):它提供了Repeatable Read事务隔离级别。它可以防止脏读和不可重复读这类的并发问题

例如:

<class-cache usage="read-only" class="com.tamakiakoo.entity.User"/>

测试

package

user.hbm.xml
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.tamakiakoo.entity">

	<class name="User" table="t_user" lazy="false">

		<id name="id" column="id">
			<generator class="native" />
		</id>
		<property name="username" />
		<property name="password" />
		<property name="email" />
		<property name="sex" />

		<set name="addSet">
			<key column="user_id" ></key>
			<one-to-many class="Address" />
		</set>
		
	</class>

</hibernate-mapping>
错误

这种二级缓冲的错误一般是由于 二级缓存没有开启,或者供应商没有写

hibernate.cfg.xml

注意: 已经注释掉了class-cache

<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
        "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
        "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">

<hibernate-configuration>

<session-factory>

		<!-- 链接数据库的四个信息 -->
		<property name="connection.driver_class">com.mysql.jdbc.Driver</property>
		<property name="connection.url">jdbc:mysql://localhost:3306/user</property>
		<property name="connection.username">root</property>
		<property name="connection.password">123456</property>

		<!-- 连接池-->
		<property name="connection.pool_size">1</property>

		<!-- 方言(每个数据库多有自己的方言)-->
		<property name="dialect">org.hibernate.dialect.MySQLDialect</property>

		<!-- 把session和当前线程绑定 -->
		<property name="current_session_context_class">thread</property>

		<!-- 是否显示sql-->
		<property name="show_sql">true</property>
		
		<!-- 1.开启二级缓存 -->
		<property name="hibernate.cache.use_second_level_cache">true</property>
		
		<!-- 2.确定二级缓存的供应商 -->
		<property name="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</property>
		

		<property name="hbm2ddl.auto">update</property>

		<mapping resource="com/tamakiakoo/entity/User.hbm.xml" />
		<mapping resource="com/tamakiakoo/entity/Address.hbm.xml" />

		<!-- 
		<class-cache usage="read-only" class="com.tamakiakoo.entity.User"/>
		<class-cache usage="read-only" class="com.tamakiakoo.entity.Address"/>
		 -->
	</session-factory>

</hibernate-configuration>

测试代码

    @Test
	public void testCache(){
		Session session = HibernateUtil.getSessionFactory().openSession();
		Transaction tr = session.beginTransaction();
		
		User user = (User)session.get(User.class, 179);
		System.out.println(user.getUsername());
		
		tr.commit();

		System.out.println("-------------------------");
		
		Session session1 = HibernateUtil.getSessionFactory().openSession();
		Transaction tr1 = session1.beginTransaction();
		
		User user1 = (User)session1.get(User.class, 179);
		System.out.println(user1.getUsername());
		
		tr1.commit();
		
	}

运行

Hibernate: select user0_.id as id0_0_, user0_.username as username0_0_, user0_.password as password0_0_,
user0_.email as email0_0_, user0_.sex as sex0_0_ from t_user user0_ where user0_.id=?
admin
-------------------------
Hibernate: select user0_.id as id0_0_, user0_.username as username0_0_, user0_.password as password0_0_,
user0_.email as email0_0_, user0_.sex as sex0_0_ from t_user user0_ where user0_.id=?
admin
结论:

没有开启class-cache并不会缓存,因为Hibernate不知道你要缓存的对象是谁

开启class-cache

	<!-- 
		<class-cache usage="read-only" class="com.tamakiakoo.entity.User"/>
		<class-cache usage="read-only" class="com.tamakiakoo.entity.Address"/>
	-->
再次运行
Hibernate: select user0_.id as id0_0_, user0_.username as username0_0_, user0_.password as password0_0_, user0_.email as email0_0_, user0_.sex as sex0_0_ from t_user user0_ where user0_.id=?
admin
-------------------------
admin
继续测试

第一次get的时候不获取集合,第二次获取集合

	@Test
	public void testCache3(){
		Session session = HibernateUtil.getSessionFactory().openSession();
		Transaction tr = session.beginTransaction();
		
		User user = (User)session.get(User.class, 179);
		System.out.println(user.getUsername());
		
		tr.commit();
		
		System.out.println("-------------------------");
		
		Session session1 = HibernateUtil.getSessionFactory().openSession();
		Transaction tr1 = session1.beginTransaction();
		
		User user1 = (User)session1.get(User.class, 179);
		System.out.println(user1.getUsername()+user1.getAddSet().size());
		
		tr1.commit();
		
		
		System.out.println("-------------------------");
		Session session2 = HibernateUtil.getSessionFactory().openSession();
		Transaction tr2 = session2.beginTransaction();
		
		User user2 = (User)session2.get(User.class, 179);
		System.out.println(user2.getUsername()+user2.getAddSet().size());
		
		tr1.commit();
		
	}
Hibernate: select user0_.id as id0_0_, user0_.username as username0_0_, user0_.password as password0_0_, user0_.email as email0_0_, user0_.sex as sex0_0_ from t_user user0_ where user0_.id=?
admin
-------------------------
Hibernate: select addset0_.user_id as user4_0_1_, addset0_.id as id1_, addset0_.id as id1_0_, addset0_.address as address1_0_, addset0_.phone as phone1_0_, addset0_.user_id as user4_1_0_ from t_address addset0_ where addset0_.user_id=?
admin3
-------------------------
Hibernate: select addset0_.user_id as user4_0_1_, addset0_.id as id1_, addset0_.id as id1_0_, addset0_.address as address1_0_, addset0_.phone as phone1_0_, addset0_.user_id as user4_1_0_ from t_address addset0_ where addset0_.user_id=?
admin3

说明:
set:很积极,用到的时候就去查询

修改配置文件

增加collection-cache配置文件

<class-cache usage="read-only" class="com.tamakiakoo.entity.Address"/>
<collection-cache配置文件 usage="read-only" collection="com.tamakiakoo.entity.User.addSet"/>

再次运行

Hibernate: select user0_.id as id0_0_, user0_.username as username0_0_, user0_.password as password0_0_, user0_.email as email0_0_, user0_.sex as sex0_0_ from t_user user0_ where user0_.id=?
admin
-------------------------
Hibernate: select addset0_.user_id as user4_0_1_, addset0_.id as id1_, addset0_.id as id1_0_, addset0_.address as address1_0_, addset0_.phone as phone1_0_, addset0_.user_id as user4_1_0_ from t_address addset0_ where addset0_.user_id=?
admin3
-------------------------
admin3

说明
只有配置了collection-cache才会缓存集合,其中collection="com.tamakiakoo.entity.User.addSet"addSet是实体类集合的名字

再次测试

利用hql查询

@Test
	public void testCache2(){
		Session session = HibernateUtil.getSessionFactory().openSession();
		Transaction tr = session.beginTransaction();
		
		Query query = session.createQuery("from User");
		List<User> list = query.list();
		System.out.println(list.size());
		
		tr.commit();
		
		System.out.println("-------------------------");
		
		Session session1 = HibernateUtil.getSessionFactory().openSession();
		Transaction tr1 = session1.beginTransaction();
		
		Query query1 = session1.createQuery("from User");
		List<User> list1 = query1.list();
		System.out.println(list1.size());
		
		tr1.commit();
		
	}
Hibernate: select user0_.id as id0_, user0_.username as username0_, user0_.password as password0_, user0_.email as email0_, user0_.sex as sex0_ from t_user user0_
6
-------------------------
Hibernate: select user0_.id as id0_, user0_.username as username0_, user0_.password as password0_, user0_.email as email0_, user0_.sex as sex0_ from t_user user0_
6

说明 :利用二级缓存,hql查询在不同同session对象中进行查询相同对象并不会在二级缓存中进行缓存.

继续测试

同一个session 使用hql查询

@Test
	public void testCache2(){
		
		Session session1 = HibernateUtil.getSessionFactory().openSession();
		Transaction tr1 = session1.beginTransaction();
		
		Query query1 = session1.createQuery("from User");
		List<User> list1 = query1.list();
		System.out.println(list1.size());
		
		
		
		Query query2 = session1.createQuery("from User");
		List<User> list2 = query2.list();
		System.out.println(list2.size());
		
		tr1.commit();
		
	}
Hibernate: select user0_.id as id0_, user0_.username as username0_, user0_.password as password0_, user0_.email as email0_, user0_.sex as sex0_ from t_user user0_
6
Hibernate: select user0_.id as id0_, user0_.username as username0_, user0_.password as password0_, user0_.email as email0_, user0_.sex as sex0_ from t_user user0_
6

说明 :利用二级缓存,hql查询在同一个session对象中进行查询相同对象并不会在二级缓存中进行缓存.

三级缓存(查询缓存)

查询缓存又称为三级缓存
查询缓存默认不使用。需要手动开启

使用步骤

  • 开启二级缓存
  • 在查询query对象,设置缓存内容(注意:存放和查询 都需要设置)
  • 二级缓存存放元数据和预定义的SQL。
<property name="hibernate.cache.use_query_cache">true</property>
开启查询缓
  • 需要先开启二级缓存
  • 确定二级缓存的供应商
hibernate.cfg.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
        "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
        "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>

	<session-factory>

		<!-- 链接数据库的四个信息 -->
		<property name="connection.driver_class">com.mysql.jdbc.Driver</property>
		<property name="connection.url">jdbc:mysql://localhost:3306/1901_mysql</property>
		<property name="connection.username">root</property>
		<property name="connection.password">root</property>

		<!-- 连接池 -->
		<property name="connection.pool_size">1</property>

		<!-- 方言(每个数据库多有自己的方言) -->
		<property name="dialect">org.hibernate.dialect.MySQLDialect</property>

		<!-- 把session和当前线程绑定 -->
		<property name="current_session_context_class">thread</property>

		<!-- 是否显示sql -->
		<property name="show_sql">true</property>

		<!-- 是否是格式化 -->
		<property name="format_sql">true</property>

		<property name="hbm2ddl.auto">update</property>

		<!-- 1.开启二级缓存 -->
		<property name="hibernate.cache.use_second_level_cache">true</property>
		
		<!-- 2.确定二级缓存的供应商 -->
		<property name="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</property>

		<!-- 开启查询缓存,开启二级缓存,确定二级缓存的供应商 -->
		<property name="hibernate.cache.use_query_cache">true</property>

		<!-- 映射文件 -->
		<mapping resource="com/qf/entity/User.hbm.xml" />
		<mapping resource="com/qf/entity/Address.hbm.xml" />

<!-- 		<class-cache usage="read-only" class="com.qf.entity.User"/> -->

	</session-factory>

</hibernate-configuration>

使用查询缓冲需要开启开关

开启开关query1.setCacheable(true);

执行代码

@Test
	public void testCache2(){
		
		Session session1 = HibernateUtil.getSessionFactory().openSession();
		Transaction tr1 = session1.beginTransaction();
		
		Query query1 = session1.createQuery("from User");
		query1.setCacheable(true);
		List<User> list1 = query1.list();
		System.out.println(list1.size());
		
		
		
		Query query2 = session1.createQuery("from User");
		query2.setCacheable(true);
		List<User> list2 = query2.list();
		System.out.println(list2.size());
		
		tr1.commit();
		
	}
Hibernate: select user0_.id as id0_, user0_.username as username0_, user0_.password as password0_, user0_.email as email0_, user0_.sex as sex0_ from t_user user0_
6
6

只有配置了 查询缓冲和二级缓存和开关(每个query都也开启,还有缓存的对象也好配置)才会进行缓存

继续测试注释掉–>缓存对象

		<!-- <class-cache usage="read-only" class="com.tamakiakoo.entity.User"/>
		<class-cache usage="read-only" class="com.tamakiakoo.entity.Address"/>
		<collection-cache usage="read-only" collection="com.tamakiakoo.entity.User.addSet"/>	 -->
执行代码

注意: 这里用的session是同一个的

/**
	 * 这个是缓存在session级别
	 */
	@Test
	public void testCache7() {

		Session s1 = HibernateUtil.getSessionFactory().openSession();

		Query query = s1.createQuery("from User u where u.id = :id");
		query.setCacheable(true); // 打开开关
		query.setInteger("id", 177);
		System.out.println(query.uniqueResult());
		
		System.out.println("===========================================");
		Query query2 = s1.createQuery("from User u where u.id = :id");
		query2.setCacheable(true); // 打开开关
		query2.setInteger("id", 177);
		System.out.println(query2.uniqueResult());
		
	}
Hibernate: select user0_.id as id0_, user0_.username as username0_, user0_.password as password0_, user0_.email as email0_, user0_.sex as sex0_ from t_user user0_ where user0_.id=?
User [id=177, username=admin, password=admin, email=null, sex=null]
===========================================
User [id=177, username=admin, password=admin, email=null, sex=null]

@Test
	public void testCache2(){
		
		Session session1 = HibernateUtil.getSessionFactory().openSession();
		Transaction tr1 = session1.beginTransaction();
		
		Query query1 = session1.createQuery("from User");
		query1.setCacheable(true);
		List<User> list1 = query1.list();
		System.out.println(list1.size());
		
		
		
		Query query2 = session1.createQuery("from User");
		query2.setCacheable(true);
		List<User> list2 = query2.list();
		System.out.println(list2.size());
		
		tr1.commit();
		
	}
Hibernate: select user0_.id as id0_, user0_.username as username0_, user0_.password as password0_, user0_.email as email0_, user0_.sex as sex0_ from t_user user0_
6
6

有缓存

继续测试

执行代码
注意: 这里用的session是不同的的

	/**
	 * 缓存在sessionFactory级别的
	 */
	@Test
	public void testCache6() {

		Session s1 = HibernateUtil.getSessionFactory().openSession();

		Query query = s1.createQuery("from User u where u.id = :id");
		query.setCacheable(true); // 打开开关
		query.setInteger("id", 177);
		System.out.println(query.uniqueResult());
		
		System.out.println("=========================================");
		
		Session s2 = HibernateUtil.getSessionFactory().openSession();
		
		Query query2 = s2.createQuery("from User u where u.id = :id");
		query2.setCacheable(true);
		query2.setInteger("id", 177);
		System.out.println(query2.uniqueResult());
	}
Hibernate: select user0_.id as id0_, user0_.username as username0_, user0_.password as password0_, user0_.email as email0_, user0_.sex as sex0_ from t_user user0_ where user0_.id=?
User [id=177, username=admin, password=admin, email=null, sex=null]
=========================================
Hibernate: select user0_.id as id0_0_, user0_.username as username0_0_, user0_.password as password0_0_, user0_.email as email0_0_, user0_.sex as sex0_0_ from t_user user0_ where user0_.id=?
User [id=177, username=admin, password=admin, email=null, sex=null]

@Test
	public void testCache2(){
		
		Session session1 = HibernateUtil.getSessionFactory().openSession();	
		Query query1 = session1.createQuery("from User");
		query1.setCacheable(true);
		List<User> list1 = query1.list();
		System.out.println(list1.size());

		
		
		Session session3 = HibernateUtil.getSessionFactory().openSession();
		Query query3 = session3.createQuery("from User");
		query3.setCacheable(true);
		List<User> list3 = query3.list();
		System.out.println(list3.size());
		
		
		
	}
Hibernate: select user0_.id as id0_, user0_.username as username0_, user0_.password as password0_, user0_.email as email0_, user0_.sex as sex0_ from t_user user0_
6
Hibernate: select user0_.id as id0_0_, user0_.username as username0_0_, user0_.password as password0_0_, user0_.email as email0_0_, user0_.sex as sex0_0_ from t_user user0_ where user0_.id=?
Hibernate: select user0_.id as id0_0_, user0_.username as username0_0_, user0_.password as password0_0_, user0_.email as email0_0_, user0_.sex as sex0_0_ from t_user user0_ where user0_.id=?
Hibernate: select user0_.id as id0_0_, user0_.username as username0_0_, user0_.password as password0_0_, user0_.email as email0_0_, user0_.sex as sex0_0_ from t_user user0_ where user0_.id=?
Hibernate: select user0_.id as id0_0_, user0_.username as username0_0_, user0_.password as password0_0_, user0_.email as email0_0_, user0_.sex as sex0_0_ from t_user user0_ where user0_.id=?
Hibernate: select user0_.id as id0_0_, user0_.username as username0_0_, user0_.password as password0_0_, user0_.email as email0_0_, user0_.sex as sex0_0_ from t_user user0_ where user0_.id=?
Hibernate: select user0_.id as id0_0_, user0_.username as username0_0_, user0_.password as password0_0_, user0_.email as email0_0_, user0_.sex as sex0_0_ from t_user user0_ where user0_.id=?
6

总结

  • 查询缓存:只要是针对HQL的查询
    • session:
      • 开启查询缓存
      • 二级缓存
      • 确定二级供应商
      • 打开开关
    • sessionFactory:
      • 开启查询缓存
      • 二级缓存
      • 确定二级供应商
      • 确定缓存对象
      • 打开开关

去重复 distinct和group by

语句
SELECT
	user0_.id AS id0_0_,
	addset1_.id AS id1_1_,
	user0_.username AS username0_0_,
	user0_. PASSWORD AS password0_0_,
	user0_.email AS email0_0_,
	user0_.sex AS sex0_0_,
	addset1_.address AS address1_1_,
	addset1_.phone AS phone1_1_,
	addset1_.user_id AS user4_1_1_,
	addset1_.user_id AS user4_0_0__,
	addset1_.id AS id0__
FROM
	t_user user0_
LEFT OUTER JOIN t_address addset1_ ON user0_.id = addset1_.user_id
查询结果

Mysql数据库中查询重复数据和去重数据 , 删除重复数据的sql及分析

CREATE TABLE `user` (
  `id` bigint(255) NOT NULL AUTO_INCREMENT,
  `name` varchar(20) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '名称',
  `age` int(2) NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

查看重复的数据

SELECT id,`name`,age,count(1)
	FROM user GROUP BY `name`,age
SELECT id,`name`,age,count(1) as c
	FROM user GROUP BY `name`,age having c > 1

删除重复的数据留下一条

SELECT MIN(id) FROM user
			GROUP BY name,age 

查询出来的id就是我们需要留下的不重复的数据的id

按理来说只要:
delete from user where id not in 子语句1

DELETE FROM user
	WHERE id NOT IN (
		SELECT MIN(id) FROM user
			GROUP BY name,age 
	)

但是报错了

DELETE FROM user
	WHERE id NOT IN (
		SELECT MIN(id) FROM user
			GROUP BY name,age 
	)
> 1093 - You can't specify target table 'user' for update in FROM clause

因为在mysql中,不能在一条Sql语句中,即查询这些数据,同时修改这些数据

解决办法

DELETE FROM user
	WHERE id NOT IN (
		SELECT temp.min_id FROM (
			SELECT MIN(id) min_id FROM user
				GROUP BY name,age
			)AS temp
	);
select * from user;
	

插入MySQL数据库前去除重复数据的几种方法

可以通过下面几个方法进行处理:

    • 使用Ingore关键字
      • INSERT IGNORE INTO
      • ignore关键字所修饰的SQL语句执行后,在遇到主键冲突时会返回一个0,代表并没有插入此条数据。如果主键是由后台生成的(如uuid),我们可以通过判断这个返回值是否为0来判断主键是否有冲突,从而重新生成新的主键key。
  • 使用on duplicate key update关键字,如插入数据时发生主键冲突就更新数据
    • insert into device values (1,2) ON DUPLICATE KEY UPDATE status =‘rowname’;
    • 插入一条数据(1,2),当有重复的主键KEY,那就更新rowname。
  • 使用replace into关键字
    • REPLACE INTO

hibernate的事务管理

什么是事务?事务就是一组操作,只有整个操作在一起才算是一个事务

事务的特性ACID
  • A(atomicity):原子性:事务不可被划分,是一个整体,要么一起成功,要么一起失败
  • C(consistence):一致性,A转100给B,A减少了100,那么B就要增加100,增加减少100就是一致的意思
  • I(isolation):隔离性,多个事务对同一内容的并发操作。
  • D(durability):持久性,已经提交的事务,就已经保存到数据库中,不能在改变了。
多个事务对同一内容同时进行操作,那么就会出现一系列的并发问题。
  • 脏读:一个事务 读到 另一个事务 没有提交的数据。
  • 不可重复读:一个事务 读到 另一个事务 已经提交的数据(update更新语句)
  • 虚度(幻读):一个事务 读到 另一个事务 已经提交的数据(insert插入语句)
事务隔离级别,用于解决隔离问题
  • read uncommitted(1) :读未提交,一个事务 读到 另一个事务 没有提交的数据,存在问题3个,解决0个问题
  • read committed(2):读已提交,一个事务 读到 另一个事务 已经提交的数据,存在问题2个,解决1个问题(脏读问题)
  • repeatable read(4):可重复读,一个事务 读到重复的数据,即使另一个事务已经提交。存在问题1个,解决2个问题(脏读、不可重复读)
  • serializable(8):单事务,同时只有一个事务可以操作,另一个事务挂起(暂停),存在问题0个,解决3个问题(脏读、不可重复读、虚读)

查看MySQL默认隔离级别:select @@global.tx_isolation,@@tx_isolation;

mysql> set global transaction isolation level read committed; //全局的

mysql> set session transaction isolation level read committed; //当前会话

hibernate中设置事务隔离级别
<!-- 设置数据库的隔离级别 -->
<property name="hibernate.connection.isolation">4</property>

为什么需要锁(并发控制)?

在多用户环境中,在同一时间可能会有多个用户更新相同的记录,这会产生冲突。这就是著名的并发性问题。

乐观锁

乐观锁(Optimistic Lock), 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改.所以不会上锁,上锁之后,当别的线程想要拿数据时,就会阻塞,直到给数据上锁的线程将事务提交或者回滚。传统的关系型数据库里就用到了很多这种锁机制,比如行锁,表锁,共享锁,排他锁等,都是在做操作之前先上锁。

创建一张表时添加一个version字段,表示是版本号

两个人同时修改名字

其中一方修改完之后就让版本号自增+1

然后另一方发现版本号不一致就不提交

行锁

又分共享锁和排他锁,由字面意思理解,就是给某一行加上锁,也就是一条记录加上锁。

注意: 行级锁都是基于索引的,如果一条SQL语句用不到索引是不会使用行级锁的,会使用表级锁。

排他锁:

名词解释:若某个事物对某一行加上了排他锁,只能这个事务对其进行读写,在此事务结束之前,其他事务不能对其进行加任何锁,其他进程可以读取,不能进行写操作,需等待其释放。

select * from student for update;

左边的线程,在事务中通过select for update语句给sid = 1的数据行上了锁。右边的线程此时可以使用select语句读取数据,但是如果也使用select for update语句,就会阻塞,使用update,add,delete也会阻塞。
  
当左边的线程将事务提交(或者回滚),右边的线程就会获取锁,线程不再阻塞

这样别人想拿这个数据就会lock直到它拿到锁。

共享锁

共享锁又称为读锁,一个线程给数据加上共享锁后,其他线程只能读数据,不能修改。

SELECT * from city where id = "1"  lock in share mode;

然后在另一个查询窗口中,对id为1的数据进行更新

update  city set name="666" where id ="1";

报错

[SQL]update  city set name="666" where id ="1";
[Err] 1205 - Lock wait timeout exceeded; try restarting transaction

那么证明,对于id=1的记录加锁成功了,在上一条记录还没有commit之前,这条id=1的记录被锁住了,只有在上一个事务释放掉锁后才能进行操作,或用共享锁才能对此数据进行操作。
再实验一下:

update city set name="666" where id ="1" lock in share mode;
[Err] 1064 - You have an error in your SQL syntax; check the manual 
corresponds to your MySQL server version for the right syntax to use near 'lock in
share mode' at line 1

加上共享锁后,也提示错误信息了,对于update,insert,delete语句会自动加排它锁

试了试

SELECT * from city where id = "1" lock in share mode;

结果ok

总结:

  • 解决并反问题的(多个线程访问同一块资源的问题)
  • 行级锁都是基于索引的,如果一条SQL语句用不到索引是不会使用行级锁的,会使用表级锁
  • 读取频繁使用乐观锁,写入频繁使用悲观锁。
  • 悲观锁:
    • 修改数据的时候总会认为别人也会修改
    • 原理:for update(数据库中锁机制)
  • 乐观锁:在修改数据的时候总会认为别人不会修改
    • 原理:通过版本号控制(增加一个vision的字段)
    • 在提交的时候判断版本哈是否一致
      • 如果一致就提交,并且版本号自增
      • 如果不一致就不让提交,说明数据已经被其他的事务修改过了
  • 共享锁又称为读锁,一个线程给数据加上共享锁后,其他线程只能读数据,不能修改。
    • 原理 lock in share mode

HIbernate锁 锁

乐观锁

Hibernate如果要使用乐观锁需要配置

模拟乐观锁


报错

悲观观锁

Hibernate二级缓存的并发访问策略

Hibernate二级缓存的并发访问策略有四种:只读(read-only)、非严格读写(nonstrict-read-write)、读写(read-write)和事务(transactional)。但是目前还没有二级缓存提供者完全支持所有的并发访问策略。

方式说明
只读(read-only):对于永远不会被修改的数据可以采用这种并发访问策略,它的并发性能是最高的。但必须保证数据不会被修改,否则就会出错。
非严格读写(nonstrict-read-write):非严格读写不能保证缓存与数据库中数据的一致性,如果存在两个事务并发地访问缓存数据的可能,则应该为该数据配置一个很短的过期时间,以减少读脏数据的可能。对于极少被修改,并且可以容忍偶尔脏读的数据可以采用这种并发策略。
读写(read-write):读写策略提供了“read committed"数据库隔离级别。对于经常被读但很少修改的数据可以采用这种策略,它可以防止读脏数据。
事务(transactional):它提供了Repeatable Read事务隔离级别。它可以防止脏读和不可重复读这类的并发问题
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值