JDBC
- JDBC编程
- 什么是JDBC
- JDBC(Java Data Base Connectivity,java数据库连接)是一种用于执行SQL语句的Java API,是用Java语言编写的类和接口组成的,可以为多种关系型数据库提供统一访问的接口。JDBC提供了一种基准,说白了,也就是sun公司为各大数据库厂商的关系型数据库连接java所制定的规范,因此他们只需要实现JDBC的接口规范即可,而具体的实现是由各大数据库厂商去实现的,由于每种数据库的独特性,Java是无法控制的,所以JDBC是一种典型的桥接模式。我们据此也可以构建更高级的工具和接口,比如封装常用的CRUD工具类,使数据库开发人员能够更快速、高效、简便的开发数据库应用程序。
- 工作原理
-
JDBC操作
/** * * @description: JDBC连接MySQL数据库进行CRUD操作 * * 步骤: * 1、加载驱动和注册数据库信息。 * 2、打开Connection,获取PreparedStatement对象。 * 3、通过PreparedStatement执行SQL,返回结果到ResultSet对象。 * 4、使用ResultSet读取数据,然后通过代码转换为具体的POJO对象。 * 5、关闭数据库相关资源,先开的后关,后开的先关。 */ public class JdbcTest {private Logger logger = LoggerFactory.getLogger(getClass());private static final String JDBC_DRIVER = "com.mysql.jdbc.Driver";private static final String URL = "jdbc://mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8";private static final String USERNAME = "root";private static final String PASSWORD = "root";private Connection getConnection() {Connection conn = null;try {// 加载驱动和注册数据库信息Class.forName(JDBC_DRIVER);DriverManager.getConnection(URL, USERNAME, PASSWORD);} catch (ClassNotFoundException | SQLException e) {logger.info("Class={JdbcTest.class.getName()} not found", JdbcTest.class.getName(), e);}return conn;}/**** @description: 保存用户信息* @param user* @return*/public int save(User user) {Connection conn = getConnection();int row = 0;// 5个问号(占位符)代表5个字段预先要保留的值String sql = "insert into tb_user (username,password,name,sex,email,tel) values(?,?,?,?,?,?)";PreparedStatement ps = null;try {/*** 使用PreparedStatement的优点:* 1、具有预编译功能,相同的SQL语句只需要编译一次,提高执行效率。* 2、可以防止SQL语句注入,提高安全性*/// 使用PreparedStatement对象里来构建并执行SQL语句ps = conn.prepareStatement(sql);// 通过PreparedStatement对象里的set方法设置要插入的值ps.setString(1, user.getUsername());ps.setString(2, user.getPassword());ps.setString(3, user.getName());ps.setInt(4, user.getSex());ps.setString(5, user.getEmail());ps.setString(6, user.getTel());// 返回影响行数row = ps.executeUpdate();} catch (SQLException e) {logger.info("Bad SQL Grammer", e);} finally {close(null, ps, conn);}return row;}/**** @description: 修改用户信息* @param user* @return*/public int update(User user) {Connection conn = getConnection();int row = 0;String sql = "update tb_user set password=? where username=?";PreparedStatement ps = null;try {ps = conn.prepareStatement(sql);ps.setString(1, user.getPassword());ps.setString(2, user.getUsername());row = ps.executeUpdate();} catch (SQLException e) {logger.info("Bad SQL Grammer", e);} finally {close(null, ps, conn);}return row;}/**** @description: 根据id删除用户* @param id* @return*/public int delete(Long id) {Connection conn = getConnection();int row = 0;String sql = "delete from tb_user where id='" + id + "'";PreparedStatement ps = null;try {ps = conn.prepareStatement(sql);row = ps.executeUpdate();} catch (SQLException e) {logger.info("Bad SQL Grammer", e);} finally {close(null, ps, conn);}return row;}/**** @description: 根据id查询用户信息* @param id* @return*/public User getAll(Long id) {Connection conn = getConnection();String sql = "select id,username,password,name,sex,email,tel from tb_user where id = ?";PreparedStatement ps = null;ResultSet rs = null;try {ps = conn.prepareStatement(sql);ps.setLong(1, id);// 通过PreparedStatement执行Sql,返回结果到ResultSet对象rs = ps.executeQuery();// 遍历结果集while (rs.next()) {Long userId = rs.getLong("id");String username = rs.getString("username");String password = rs.getString("password");String name = rs.getString("name");Integer sex = rs.getInt("sex");String email = rs.getString("email");String tel = rs.getString("tel");User user = new User();user.setId(userId);user.setUsername(username);user.setPassword(password);user.setName(name);user.setSex(sex);user.setEmail(email);user.setTel(tel);return user;}} catch (SQLException e) {logger.info("Bad SQL Grammer", e);}return null;}/**** @description: 释放资源,注意:先开的后关,后开的先关* @param rs* @param ps* @param conn*/public void close(ResultSet rs, PreparedStatement ps, Connection conn) {try {if (rs != null && rs.isClosed()) {rs.close();}} catch (SQLException e1) {e1.printStackTrace();}try {if (ps != null && ps.isClosed()) {ps.close();}} catch (SQLException e) {e.printStackTrace();}try {if (conn != null && conn.isClosed()) {conn.close();}} catch (SQLException e) {e.printStackTrace();}} }
- 弊端
Hibernate
- 什么是Hibernate
- Hibernate翻译过来是冬眠的意思,其实对于对象来说就是持久化。
- Hibernate是一个开放源代码的对象关系映射框架,它对JDBC进行了非常轻量级的对象封装,它将POJO与数据库表建立映射关系,是一个全自动的ORM框架,hibernate可以自动生成SQL语句,自动执行,使得Java程序员可以随心所欲的使用对象编程思维来操纵数据库。
- Hibernate可以应用在任何使用JDBC的场合,既可以在Java的客户端程序使用,也可以在Servlet/JSP的Web应用中使用,最具革命意义的是,Hibernate可以在应用EJB的J2EE架构中取代CMP(Container-Managed Persistence),完成数据持久化的重任。
- 工作原理
- 图解
- 只有处于Session管理下的POJO才具有持久化操作能力。当应用程序对于处于Session管理下的POJO实例执行操作时,Hibernate将这种面向对象的操作转换成了持久化操作能力。
- Hibernate需要一个hibernate.properties文件,该文件用于配置Hibernate和数据库连接的信息。还需要一个XML文件,该映射文件确定了持久化类和数据表、数据列之间的想对应关系。除了使用hibernate.properties文件,还可以采用另一种形式的配置文件:*.cfg.xml文件。在实际应用中,采用XML配置文件的方式更加广泛,两种配置文件的实质是一样的。
- Hibernate的持久化解决方案将用户从赤裸裸的JDBC访问中释放出来,用户无需关注底层的JDBC操作,而是以面向对象的方式进行持久层操作。底层数据连接的获得、数据访问的实现、事务控制都无需用户关心。这是一种“全面解决”的体系结构方案,将应用层从底层的JDBC/JTA API中抽象出来。通过配置文件来管理底层的JDBC连接,让Hibernate解决持久化访问的实现
- (1)SessionFactory:这是Hibernate的关键对象,它是单个数据库映射关系经过编译后的内存镜像,它也是线程安全的。它是生成Session的工厂,本身要应用到ConnectionProvider,该对象可以在进程和集群的级别上,为那些事务之间可以重用的数据提供可选的二级缓存。
- (2)Session:它是应用程序和持久存储层之间交互操作的一个单线程对象。它也是Hibernate持久化操作的关键对象,所有的持久化对象必须在Session的管理下才能够进行持久化操作。此对象的生存周期很短,其隐藏了JDBC连接,也是Transaction 的工厂。Session对象有一个一级缓存,现实执行Flush之前,所有的持久化操作的数据都在缓存中Session对象处。
- (3)持久化对象:系统创建的POJO实例一旦与特定Session关联,并对应数据表的指定记录,那该对象就处于持久化状态,这一系列的对象都被称为持久化对象。程序中对持久化对象的修改,都将自动转换为持久层的修改。持久化对象完全可以是普通的Java Beans/POJO,唯一的特殊性是它们正与Session关联着。
- (4)瞬态对象和脱管对象:系统进行new关键字进行创建的Java 实例,没有Session 相关联,此时处于瞬态。瞬态实例可能是在被应用程序实例化后,尚未进行持久化的对象。如果一个曾今持久化过的实例,但因为Session的关闭而转换为脱管状态。
- (5)事务(Transaction):代表一次原子操作,它具有数据库事务的概念。但它通过抽象,将应用程序从底层的具体的JDBC、JTA和CORBA事务中隔离开。在某些情况下,一个Session 之内可能包含多个Transaction对象。虽然事务操作是可选的,但是所有的持久化操作都应该在事务管理下进行,即使是只读操作。
- (6)连接提供者(ConnectionProvider):它是生成JDBC的连接的工厂,同时具备连接池的作用。他通过抽象将底层的DataSource和DriverManager隔离开。这个对象无需应用程序直接访问,仅在应用程序需要扩展时使用。
- (7)事务工厂(TransactionFactory):它是生成Transaction对象实例的工厂。该对象也无需应用程序的直接访问。
- Hibernate进行持久化操作离不开SessionFactory对象,这个对象是整个数据库映射关系经过编译后的内存镜像,该对象的openSession()方法可打开Session对象。SessionFactory对想是由Configuration对象产生。
- 图解
- 对Hibernate的理解
- Hibernate是对JDBC进一步封装
- 原来没有使用Hiberante做持久层开发时,存在很多冗余,如:各种JDBC语句,connection的管理,所以出现了Hibernate把JDBC封装了一下,不用操作数据,直接操作它就行了。
- 分层角度
- 非常典型的三层架构:表示层,业务层,还有持久层。Hiberante也是持久层的框架,而且持久层的框架还有很多,比如:IBatis,Nhibernate,JDO,OJB,EJB等等。
- 开源ORM(对象关系映射)框架
- Hibernate是对JDBC进一步封装
- Hibernate的核心API
- 图解
- Configuration接口:负责配置并启动Hibernate
- SessionFactory接口:负责初始化Hibernate
- Session接口:负责持久化对象的CRUD操作
- Transaction接口:负责事务,对底层操作进行封装
- Query接口和Criteria接口:负责执行各种数据库查询
- Configuration实例是一个启动期间的对象,一旦SessionFactory创建完成它就被丢弃了。SessionFactory 线程安全,重量级,对象创建代价很高,通常在应用程序启动时创建,应用退出时关闭。Session 线程不安全,轻量级(创建和销毁不需要消耗太多资源),避免多个线程使用一个实例,通过SessionFactory打开,Session是hibernate的一级缓存。Query接口包装了一个HQL查询语句。Criteria接口擅长于执行动态查询
- 图解
- Hibernate优点
- Hibernate缺点
- 使用数据库特性的语句,将很难调优
- 对大批量数据更新存在问题
- 系统中存在大量的攻击查询功能
- Hibernate映射
- 映射概念
- 映射文件
- 分类
- Hibernate事务
- 事务
- 事务是指一组相互信赖的操作行为,这些操作要么必须全部成功,要么必须全部失败,以保证数据的一致性和完整性。事务的成功取决于工作单元的所有SQL语句都执行成功,它必须具备ACID特征,ACID是Atomic(原子性)、Consistency(一致性)、Isolation(隔离性)和持久性(Durability),它们的含义是:
- 原子性:事务是应用中最小执行单位,不可再分割的逻辑执行体,所有操作执行成功事务才算成功
- 一致性:事务不能破坏数据的完整性和一致性(正确性)
- 隔离性:在并发环境中,事务是独立的,它不依赖其他事务也能完成任务
- 持久性:只要事务成功执行,数据永久保存下来
- Hibernate不能跨事务
- JTA(Java Transaction API)可以操作多个数据库
- 事务是指一组相互信赖的操作行为,这些操作要么必须全部成功,要么必须全部失败,以保证数据的一致性和完整性。事务的成功取决于工作单元的所有SQL语句都执行成功,它必须具备ACID特征,ACID是Atomic(原子性)、Consistency(一致性)、Isolation(隔离性)和持久性(Durability),它们的含义是:
- 事务边界
- 1、数据库系统支持以下两种事务模式:
- (1) 自动提交模式:每一个SQL语句都是一个独立的事务,如果执行成功就自动提交,否则自动回滚
- (2) 手工提交模式:由程序显式指定事务边界,手工提交模式下运行事务,必须显式指定事务边界:
- 开始事务:begin transaction
- 提交事务:commit transaction
- 回滚(撤销)事务:rollback transaction
- 2、通过JDBC API声明事务边界
- java.sq.Connection类提供了以下用于控制事务的方法
- setAutoCommit(boolean autoCommit):设置是事自动提交事务
- commit():提交事务
- rollback():撤销事务
- java.sq.Connection类提供了以下用于控制事务的方法
- 3、通过Hibernate API声明事务边界
- Hibernate API封装了JDBC API和JTA API。应用程序可以绕过Hibernate API直接通过JDBC API和JTA API来声明事务,但是这不利于跨平台开发。
- 从SessionFactory中获得Session实例有两种方式:
- 方法一
Session session=sessionFactory.openSession();//从连接池中获得连接
- 方法二
Connection con=DriverManager.getConnection(url,user,pwd); //这种方式绕过Hibernate con.setAutoCommit(false); //把连接设为手工提交事务模式 Session session=sessionFactory.openSession(con);
- 方法一
- 在Hibernate API中,Session和Transaction类提供了以下声明事务的方法
Transaction tx=session.beginTransaction();//开始事务 tx.commit();//提交事务,调用flush()方法清理缓存,然后提交事务 tx.rollback();//撤销事务
- 要注意的内容:
- 尽量让一个Session对应一个事务,不管事务成功与否最后要关闭Sessin,让其清空缓存,释放占用的连接;如果事务仅包含只读(select)操作,也应在执行成功后提交事务,让数据库释放事务所占的资源。
Session session=sessionFactory.openSession(); Transaction tx; try{ tx=session.beginTransaction();//开始一个事务 ….//执行一些操作 tx.commit();//提交事务 }catch(Exception e){ tx.rollback();//撤销事务。这个语句也要捕获异常,代码略 }finally{ session.close();//撤销事务。这个语句也要捕获异常,代码略 }
- 一个Session可以对应多个事务,这种方式优点重用缓存中的持久化对象。在一个事务没提交之前,不可以开始第二个事务(不允许的);如果Session的一个事务出现了异常,就应关闭这个Session。如果仍然用它执行其他事务是不可取的。
try{ tx1=session.beginTransaction(); ….//执行一些操作 tx1.commit();//提交事务 session.desconnect();//释放数据连接 ….//执行一些操作,这些操作不属于任何事务 session.reconnnect();//重新获得数据库连接 tx2=session.beginTranction();//开始第二个事务 ….// 执行一些操作 tx2.commit();//提交第二个事务 }catch(Exception e){ if(tx1!=null)tx1.rollback(); if(tx2!=null)tx2.rollback(); }finally{ session.close(); }
- 多个事务并发引起的问题
- 多个事务同时访问数据库中相同的数据时,如果没有采取必要的隔离机制,就可能会发生如下并发问题:
- 第一类丢失更新:两个事务都更新同一个行,而另一个事务异常回滚,导致两处变化都丢失。这种问题是由于完全没有设置事务的隔离级别造成的。
- 脏读:一个事务读取到另一个事务尚未提交的更改数据。
- 不可重复读:一个事务两次读取同一行数据,两次的状态不同。A取数据,B更改数据,A再次取数据。
- 第二类丢失更新:一个事务覆盖另一个事务已经提交的数据。
- 幻读:一个事务前后执行一个查询两次,在第二个结果集中包括第一个结果集中不可见的行,或者包括已经删除的行时。跟不可重复读有什么区别??
- 事务隔离级别
- 为了解决多个事务并发会引发的问题,让用户根据需要在事务的隔离性和并发性之间做合理的权衡,数据库系统提供了四种事务隔离级别供用户选择:
- Read Uncommitted(读未提交数据)
- Read Committed(读已提交数据)
- Repeatable Read(可重复读)
- Serializable(串行化)
- 隔离级别依次为1,2,4,8,一般选隔离级别2。
- Hibernate配置文件中可以显示地设置隔离级别。每一种隔离级别对应着一个正整数,在hibernate配置文件中设置:
<session-factory><property name="connection.isolation">2</property> </session-factory>
- 为了解决多个事务并发会引发的问题,让用户根据需要在事务的隔离性和并发性之间做合理的权衡,数据库系统提供了四种事务隔离级别供用户选择:
- 乐观锁和悲观锁
- 当数据库系统采用Red Committed隔离级别时,会导致不可重复读和第二类更新丢失的并发问题,在可能出现这种问题的场合,可以在应用程序中采用乐观锁或者悲观锁来避免这类问题,在企业开发中:
- Read committed+乐观锁 => Repeatable Read
- 1、乐观锁的原理和应用
- 2、悲观锁的原理和应用
- 它会生成 select .... for update 这样的语句来显示指定采用独占锁来锁定查询的记录。
- 事务提交前,不可以对这条数据进行修改。
session.load(XXX.class, params, LockOptions.UPGRADE);
- Session与事务
- Hibernate的事务是通过Session的beginTransaction()方法显示打开,Hibernate自身并不提供事务控制行为,Hibernate底层直接使用JDBC连接、JTA资源或其他资源的事务。
- Hibernate只是对底层事务进行了抽象,让应用程序可以直接面向Hibernate事务编程,从而将应用程序和JDBC连接、JTA资源或其他资源的事务隔离开了。
- 从编程角度来看,Hibernate的事务由Session对象开启;从底层实现来看,Hibernate的事务由JDBCTransactionFactory(针对JDBC局部事务环境的实现类)、JTATransactionFactory(针对JTA局部事务环境的实现类)。
- 应用程序编程无需手动操作TransactionFactory产生事务,这是因为SessionFactroy底层已经封装了TransactionFactory。
- Hibernate的所有持久化访问都必须在Session管理下进行。
- Hibernate建议采用每个请求对应一次Session的模式。
- 尽量让一个Session对应一个事务,不管事务成功与否最后要关闭Sessin,让其清空缓存,释放占用的连接;如果事务仅包含只读(select)操作,也应在执行成功后提交事务,让数据库释放事务所占的资源。
- 要注意的内容:
- 1、数据库系统支持以下两种事务模式:
- 事务
- Hibernate缓存机制
- 缓存简介 缓存是介于应用程序和物理数据源之间,其作用是为了降低应用程序对物理数据源访问的频次,从而提高了应用的运行性能。缓存内的数据是对物理数据源中的数据的复制,其作用是为了降低应用程序对物理数据源访问的频次,从而提高了应用的运行性能。
- 缓存的范围
- 事务范围:缓存只能被当前事务访问。缓存的生命周期依赖于事务的生命周期,当事务结束时,缓存也就结束生命周期。在此范围下,缓存的介质是内存。事务可以是数据库事务或者应用事务,每个事务都有独自的缓存,缓存内的数据通常采用相互关联的的对象形式。
- 进程范围:缓存被进程内的所有事务共享。这些事务有可能是并发访问缓存,因此必须对缓存采取必要的事务隔离机制。缓存的生命周期依赖于进程的生命周期,进程结束时,缓存也就结束了生命周期。进程范围的缓存可能会存放大量的数据,所以存放的介质可以是内存或硬盘。缓存内的数据既可以是相互关联的对象形式也可以是对象的松散数据形式。松散的对象数据形式有点类似于对象的序列化数据,但是对象分解为松散的算法比对象序列化的算法要求更快。
- 集群范围:在集群环境中,缓存被一个机器或者多个机器的进程共享。缓存中的数据被复制到集群环境中的每个进程节点,进程间通过远程通信来保证缓存中的数据的一致性,缓存中的数据通常采用对象的松散数据形式。
- Hibernate缓存
- Hibernate是一个持久层框架,经常访问物理数据库。
- Hibernate缓存是为了降低应用程序对物理数据源访问的频次,从而提高应用程序的运行性能。
- 缓存内的数据是对物理数据源中的数据的复制,应用程序在运行时从缓存读写数据,在特定的时刻或事件会同步缓存和物理数据源的数据。
- 只有处于Session管理下的POJO才具有持久化操作能力。当应用程序对于处于Session管理下的POJO实例执行操作时,Hibernate将这种面向对象的操作转换成了持久化操作能力。
- Hibernate包括两个级别的缓存:
- 默认总是开启的Session级别的一级缓存
- 可选的SessionFactory级别的二级缓存
- 第一级别的Session级别的缓存,是属于事务范围的缓存。这一级别的缓存由hibernate管理的,一般情况下无需进行干预;
- 第二级别的SessionFactory级别的缓存,它是属于进程范围或集群范围的缓存。这一级别的缓存可以进行配置和更改,并且可以动态加载和卸载。
- Hibernate还为查询结果提供了一个查询缓存,它依赖于第二级缓存。
- Hibernate中没有二级缓存,整合第三方插件,默认使用插件encache。
- 安装encache插件:
- 将hibernate-release-5.2.10.Final\lib\optional\ehcache下所有jar包拷到项目下
- 将hibernate-release-5.2.10.Final\project\etc下ehcache.xml文件拷到src下
- 打开文件hibernate-release-5.2.10.Final\project\etc\hibernate.properties,找到Second-level Cache 二级缓存参数:hibernate.cache.use_second_level_cache true 启用二级缓存hibernate.cache.region.factory_class org.hibernate.cache.encache.EhCacheRegionFactory启用二级缓存插件(包名可能会错,根据类实际所在的包改正)
- 在类hmb.xml文件中,class下或集合下配置<cache>
- 在ehcache.xml中配置实体类或者集合
- Hibernate是一个持久层框架,经常访问物理数据库。
- 一级缓存
- Hibernate一级缓存又称之为”Session缓存”,”会话期缓存”,顾名思义就是在会话期才会执行的缓存机制。一种轻量级的实现。session销毁时跟随销毁。
- 缓存位置:session中
- 生命周期:一个事务中
- 缓存规格:{ ID:实体 }
- 默认开启
- 何时数据会进入缓存:事务中加载过的数据,都会进入缓存,并以{ID:实体}存储在session中。
- 何时可以检查缓存:以ID为条件的查询可以检查缓存。
session.get();//可以检查 Query.list();//不能检查 Query.uniqueResult();//不能检查 Query.iterate();//可以检查
- 细节:iterate() 运作流程
String hql="from User u where u.name=?"; Query.iterate(hql);
- (1)保留查询条件,到数据库中查询ID [1,2,3]
select id from t_user where t_name=?
- (2)通过查到的ID去检查缓存。如果有缓存可用,则不用再查询数据库。但是,注意,如果没有缓存可用,则要再次发起对数据库的查询:
select * from t_user where t_id=3; select * from t_user where t_id=2; select * from t_user where t_id=1;
- 综上,再使用iterate()方法时,可能导致n+1次查询问题。n=满足条件的数据行数。存在iterator的原因是,有可能会在一个session中查询两次数据,如果使用list每一次都会把所有的对象查询上来,而是要iterator仅仅只会查询id,此时所有的对象已经存储在一级缓存(session的缓存)中,可以直接获取
- (3)使用:
Iterator it=query2.iterate(); while(it.hasNext()){ User user=(User)it.next(); System.out.println(user);}
- 相关的API:
- evict(obj):用于将某个对象从Session的一级缓存中清除
- clear():用于将一级缓存中的所有的对象全部清除
- 二级缓存
- 二级缓存的出现就是为了弥补一级缓存的生存期局限于session的生存期内,便于其他的session也能时用到缓存中的数据。即每个session都会共享的缓存,这就是Hibernate的二级缓存机制。
- 缓存位置:SessionFactory中
- 生命周期:全局可用
- 缓存规格:{ID:实体}
- 默认关闭:通过配置开启。
<!-- 开启二级缓存 --> <property name="hibernate.cache.use_second_level_cache">true</property> <!-- 二级缓存类别:EhCache,OSCache,JbossCache --> <property name="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</property>
- 导包并引入ehcahe.xml,为要进入二级缓存的实体,增加权限
//只读缓存权限 //@Cache(usage=CacheConcurrencyStrategy.READ_ONLY) //读写缓存权限 @Cache(usage=CacheConcurrencyStrategy.READ_WRITE)
- 二级缓存的使用策略一般有这几种:read-only、nonstrict-read-write、read-write、transactional。
- 注意:我们通常使用二级缓存都是将其配置成 read-only ,即我们应当在那些不需要进行修改的实体类上使用二级缓存,否则如果对缓存进行读写的话,性能会变差,这样设置缓存就失去了意义。
- 何时数据会进入缓存:事务中加载过的数据,都会进入缓存,并以{ID:实体}存储在session中。
- 何时可以检查缓存:以ID为条件的查询可以检查缓存
get(); iterate();
- 查询缓存:依赖二级缓存
- 缓存位置:SessionFactory中
- 生命周期:全局可用,但不稳定,如果和缓存数据相关的表有任何的改动,则缓存数据失效,在一般的应用程序中,sessionfactory会以单例的形式存在,所以在整个应用程序的生命周期里,sessionfactory会一直存在。即二级缓存也一直存在直到关闭应用程序。
- 缓存规格: {hql:查询结果(字段)}
- 默认关闭:<property name="hibernate.cache.use_query_cache">true</property>
- 在查询前:query.setCacheable(true); //本次查询要使用查询缓存
- 何时数据会进入缓存:用hql查询字段的查询结果,都可以进入查询缓存:{HQL:结果(字段)}
- 何时可以检查缓存:只要再次用同样的hql查询,则可以检查查询缓存。
- 使用场景
- 什么样的数据适合存放到第二级缓存中?
- 很少被修改的数据
- 不是很重要的数据,允许出现偶尔并发的数据
- 不会被并发访问的数据
- 参考数据(字典表,码值表)
- 不适合存放到第二级缓存的数据?
- 经常被修改的数据
- 财务数据,绝对不允许出现并发
- 与其他应用共享的数据。
- 什么样的数据适合存放到第二级缓存中?
- 细节:如果查询的实体,则查询缓存只能缓存:{HQL:实体的ID字段}
- 缓存总结
- 一级缓存,二级缓存,缓存的是实体:{ID:实体}
- 查询缓存:{HQL : 查询结果}
- 如果查询时是查询字段的话:select a,b,c,d from XXX; 查询缓存足矣。
- 如果查询时是查询实体的话:from User u; 二级缓存+查询缓存。
- 查询缓存和二级缓存的联合使用:
- 当我们如果需要查询出两次对象的时候,可以使用二级缓存来解决N+1的问题。
- 如果查询时是查询实体的话:from User u;
- 初次query.list(); 时,检查查询缓存,没有可用数据,则转向数据库,获得一个实体User,
- 将{HQL:User的ID}存入查询缓存,将{User的ID:User}存入二级缓存。
- 再次query.list(); 时,检查查询缓存,获得缓存数据:User的ID,通过UserID检查二级缓存,
- 如果有数据,则直接使用,否则以各个ID为条件分别发起查询。
- 一二级缓存对比
- 设计原因
- 一般情况下,我们查询的数据一般是实时的,使用二级缓存肯定不行,使用一级缓存既利用了缓存又不会影响实时。使用二级缓存是为了存储一些比较稳定的数据,二级缓存策略,是针对于ID查询的缓存策略,对于条件查询则毫无作用。为此,Hibernate提供了针对条件查询的Query缓存。
- 图解
- Hibernate方法详解
- Session.flush( )方法的作用其实就是让session的缓存的数据(session就是一级缓存)刷入到数据库里面去,让数据库同步,可以更简单的理解成,强制让session的数据和数据库的数据同步,而不是什么清除缓存。清除缓存是session.clear()方法,在使用flush方法一般之前都是对一个对象进行CRUD的操作,然后调用flush方法,就及时的同步到数据库里面去,其实session.flush()方法用的最好的一块是在处理大量数据的时候我们可以控制数量,比如,我们要存储1万个对象,我们可以这样做:
if(i%20 == 0){ session.flush(); // 强制同步数据到数据库里面去 session.clear();清除缓存 }
- 这样提高工作性能。
- clear 方法
- 把缓冲区内的全部对象清除,但不包括操作中的对象。
- 无论是Load 还是 Get 都会首先查找缓存(一级缓存) 如果没有,才会去数据库查找,调用Clear() 方法,可以强制清除Session缓存。
session.get(Teacher.class, 3); //这里不clear只会执行一次sql语句,有clear会执行2次 //session.clear(); session.get(Teacher.class, 3);
- 这里虽然用了2 个 get 方法( get 方法会立即执行 sql 语句),但因为第一次执行后会缓存一个 ID 为 3 的实体,所以虽然有 2 个 get 方法只执行一次 SQL 语句。
- flush方法
- 刷新一级缓存区的内容,使之与数据库数据保持同步。
- flush方法是可以设置的,也就是 flush 什么时候执行是可以设置的
- 在session.beginTransaction 前设置 FlushMode
- session.setFlushMode(FlushMode.Always|AUTO|COMMIT|NEVER|MANUAL)
- FlushMode有 5 个值可选
- Always:任何代码都会 Flush
- AUTO:默认方式 – 自动
- Commit:COMMIT时
- Never:始终不
- MANUAL:手动方式
- 1、NEVEL:已经废弃了,被MANUAL取代了
- 2、MANUAL:只能用flush来清理缓存,commit不会。
- 如果FlushMode是MANUAL或NEVEL,在操作过程中hibernate会将事务设置为readonly,所以在增加、删除或修改操作过程中会出现如下错误:
org.springframework.dao.InvalidDataAccessApiUsageException: Write operations are not allowed in read-only mode (FlushMode.NEVER)
- turn your Session into FlushMode.AUTO or remove 'readOnly' marker from transaction definition ;
- 解决办法:配置事务,spring会读取事务中的各种配置来覆盖hibernate的session中的FlushMode;
- 3、AUTO
- 4、COMMIT
- 提交事务或者session.flush()时,刷新数据库;查询不刷新。
- 5、ALWAYS:
- 每次进行查询、提交事务、session.flush()的时候都会刷数据库。
- ALWAYS和AUTO的区别:当hibernate缓存中的对象被改动之后,会被标记为脏数据(即与数据库不同步了)。当 session设置为AUTO时,hibernate在进行查询的时候会判断缓存中的数据是否为脏数据,是则刷数据库,不是则不刷,而always是直接刷新,不进行任何判断。很显然auto比always要高效得多。
- 设置FlushMode有个好处是可以节省开销,比如默认session只做查询时,就可以不让他与数据库同步了。
- evict方法
- evict(Object obj) :将指定的持久化对象从一级缓存中清除。session.evict(obj);
- evict(Class arg0, Serializable arg1):将某个类的指定ID的持久化对象从二级缓存中清除,释放对象所占用的资源。sessionFactory.evict(Customer.class, new Integer(1));
- evict(Class arg0):将指定类的所有持久化对象从二级缓存中清除,释放其占用的内存资源。sessionFactory.evict(Customer.class);
- evictCollection(String arg0) 将指定类的所有持久化对象的指定集合从二级缓存中清除,释放其占用的内存资源。sessionFactory.evictCollection("Customer.orders");
- 释放对象所占用的内存资源,指定对象从持久化状态变为脱管状态,从而成为游离对象。
- contains方
- contains(Object obj) 判断指定的对象是否存在于一级缓存中。
- Hibernate 执行的顺序如下
- (1) 生成一个事务的对象,并标记当前的 Session 处于事务状态(注:此时并未启动数据库级事务)。
- (2) 应用使用 s.save 保存对象,这个时候 Session 将这个对象放入 entityEntries ,用来标记对象已经和当前的会话建立了关联,由于应用对对象做了保存的操作, Session 还要在 insertions 中登记应用的这个插入行为(行为包括:对象引用、对象 id 、 Session 、持久化处理类)。
- (3)s.evict 将对象从 s 会话中拆离,这时 s 会从 entityEntries 中将这个对象移出。
- (4) 事务提交,需要将所有缓存 flush 入数据库, Session 启动一个事务,并按照 insert,update,……,delete 的顺序提交所有之前登记的操作(注意:所有 insert 执行完毕后才会执行 update ,这里的特殊处理也可能会将你的程序搞得一团糟,如需要控制操作的执行顺序,要善于使用flush ),现在对象不在 entityEntries 中,但在执行 insert 的行为时只需要访问 insertions 就足够了,所以此时不会有任何的异常。异常出现在插入后通知 Session 该对象已经插入完毕这个步骤上,这个步骤中需要将 entityEntries 中对象的 existsInDatabase 标志置为 true ,由于对象并不存在于 entityEntries 中,此时 Hibernate 就认为 insertions 和 entityEntries 可能因为线程安全的问题产生了不同步(也不知道 Hibernate 的开发者是否考虑到例子中的处理方式,如果没有的话,这也许算是一个 bug 吧),于是一个 net.sf.hibernate.AssertionFailure 就被抛出,程序终止。
- 一般我们会错误的认为 s.save 会立即执行,而将对象过早的与 Session 拆离,造成了 Session 的 insertions 和entityEntries中内容的不同步。所以我们在做此类操作时一定要清楚Hibernate什么时候会将数据 flush 入数据库,在未flush之前不要将已进行操作的对象从Session上拆离。解决办法是在 save 之后,添加 session.flush。
- Session.flush( )方法的作用其实就是让session的缓存的数据(session就是一级缓存)刷入到数据库里面去,让数据库同步,可以更简单的理解成,强制让session的数据和数据库的数据同步,而不是什么清除缓存。清除缓存是session.clear()方法,在使用flush方法一般之前都是对一个对象进行CRUD的操作,然后调用flush方法,就及时的同步到数据库里面去,其实session.flush()方法用的最好的一块是在处理大量数据的时候我们可以控制数量,比如,我们要存储1万个对象,我们可以这样做:
-
Hibernate·主键生成策略
1.Assigned 由用户生成主键值,并且要在save()之前指定否则会抛出异常特点:主键的生成值完全由用户决定,与底层数据库无关。用户需要维护主键值,在调用session.save()之前要指定主键值。2.Hilo Hilo使用高低位算法生成主键,高低位算法使用一个高位值和一个低位值,然后把算法得到的两个值拼接起来作为数据库中的唯一主键。Hilo方式需要额外的数据库表和字段提供高位值来源。默认情况下使用的表是hibernate_unique_key,默认字段叫作next_hi。next_hi必须有一条记录否则会出现错误。特点:需要额外的数据库表的支持,能保证同一个数据库中主键的唯一性,但不能保证多个数据库之间主键的唯一性。Hilo主键生成方式由Hibernate 维护,所以Hilo方式与底层数据库无关,但不应该手动修改hilo算法使用的表的值,否则会引起主键重复的异常。3.Increment Increment方式对主键值采取自动增长的方式生成新的主键值,但要求底层数据库的主键类型为long,int等数值型。主键按数值顺序递增,增量为1。特点:由Hibernate本身维护,适用于所有的数据库,不适合多进程并发更新数据库,适合单一进程访问数据库。不能用于群集环境。4.Identity Identity方式根据底层数据库,来支持自动增长,不同的数据库用不同的主键增长方式。特点:与底层数据库有关,要求数据库支持Identity,如MySQl中是auto_increment, SQL Server 中是Identity,支持的数据库有MySql、SQL Server、DB2、Sybase和HypersonicSQL。 Identity无需Hibernate和用户的干涉,使用较为方便,但不便于在不同的数据库之间移植程序。5.Sequence Sequence需要底层数据库支持Sequence方式,例如Oracle数据库等特点:需要底层数据库的支持序列,支持序列的数据库有DB2、PostgreSql、Oracle、SAPDb等在不同数据库之间移植程序,特别从支持序列的数据库移植到不支持序列的数据库需要修改配置文件。6.Native Native主键生成方式会根据不同的底层数据库自动选择Identity、Sequence、Hilo主键生成方式特点:根据不同的底层数据库采用不同的主键生成方式。由于Hibernate会根据底层数据库采用不同的映射方式,因此便于程序移植,项目中如果用到多个数据库时,可以使用这种方式。7.UUID UUID使用128位UUID算法生成主键,能够保证网络环境下的主键唯一性,也就能够保证在不同数据库及不同服务器下主键的唯一性。特点:能够保证数据库中的主键唯一性,生成的主键占用比较多的存贮空间8.Foreign GUID Foreign用于一对一关系中。GUID主键生成方式使用了一种特殊算法,保证生成主键的唯一性,支持SQL Server和MySQL。
-
Hibernate Session
1.HttpSession与Hibernate Session HttpSession是severlet中的会话机制,也是jsp的内置对象,可以简单看做是个存储对象的一个作用域。而Hibernate Session是把JDBC的Connection和Transaction接口进行了简单的封装后的一个接口,即此Session主要用来管理对象的增删改查和事务,还有只要持久化类的实例对象与Session关联了,那此对象就不只是简单的在内存中了,而是可以通过Session对象去管理它了,所以也称此对象在Session缓存中。2.Session的延迟加载 Session的延迟加载实现要解决两个问题:正常关闭连接和确保请求中访问的是同一个session。Hibernate session就是java.sql.Connection的一层高级封装,一个session对应了一个Connection。http请求结束后正确的关闭session(过滤器实现了session的正常关闭);延迟加载必须保证是同一个session(session绑定在ThreadLocal)。
-
Hibernate 创建Configuration对象
org.hibernate.cfg.Configuration实例代表一个应用程序到SQL数据库的映射配置,Configuration提供了一个buildSessionFactory()方法,该方法可以产生一个不可变的SessionFactory对象。可以直接实例化Configuration来获取一个实例,并为它指定一个Hibernate映射文件,如果映射文件在类加载路径中,则可以使用addResource()方法来添加映射定义文件。那么现在的问题就是如何创建Configuration对象呢?随着Hibernate 所使用的配置文件的不同,创建Configuration对象的方式也不相同。通常有几种配置Hibernate的方式:第一种是使用hibernate.properties文件作为配置文件。第二种是使用hibernate.cfg.xml文件作为配置文件。第三种是不使用任何的配置文件,以编码方式来创建Configuration对象。请注意:Configuration对象的唯一作用就是创建SessionFactory实例,所以它才被设计成为启动期间对象,而一旦SessionFactory对象创建完成,它就被丢弃了。1. 使用hibernateproperties作为配置文件 对于hibernate.properties作为配置文件的方式,比较适合于初学者。因为初学者往往很难记住该配置文件的格式,以及需要配置哪些属性。在Hibernate发布包的etc路径下,提供了一个hibernate.properties文件,该文件列出了Hibernate 的所有属性。每个配置段都给出了大致的注释,用户只要取消所需配置段的注释,就可以快速配置Hibernate和数据库的链接此处给出使用hibernate.properties文件创建Configuration对象的方法。//实例化configuration对象Configuration cfg = new Configuration()//多次调用addResource()方法,添加映射文件.addResource("Item.hbm.xml").addResource("Bid.hbm.xml");查看hibernate.properties文件发现,该文件没有提供Hibernate映射文件的方式。因此使用hibernate.properties文件来作为配置文件时,必须使用Configuration的.addResource()方法,使用该方法来添加映射文件。注意:正如上面的代码所示,使用hibernate.properties文件配置Hibernate的属性固然简单,但是因为要手动添加映射文件,当映射文件极其多时,这是一件非常催人泪下的事情。这也就是在实际开发中,不常使用hibernate.properties文件作为配置文件的原因。当然还有另一种添加配置文件的策略,因为映射文件和持久化类是一一对应的,可以通过Configuration对象来添加持久化类,让Hibernate自己来搜索映射文件。//实例化configuration对象Configuration cfg = new Configuration)//多次调用addClass()方法,直接添加持久化类.addClass(ppp.Item.class).addClass(ppp.BId.class); 2. 使用hibernate.cfg.xml作为配置文件 前面已经看到使用hibernate.properties作为配置文件的情形。因为hibernate.cfg.xml中已经添加了hibernate的映射文件,采用这种配置文件创建configuration对象实例由以下代码实现://实例化configuration对象Configuration cfg = new Configuration().configure() ;//configure()方法将会负责加载hibernate.cfg.xml文件需要注意的是:在通过new关键字创建Configuration对象之后,不要忘记调用configure()方法。3. hibernate.properties和hiberntae.cfg.xml文件 如果使用etc路径下的hibernate.properties文件作为配置文件的模板,修改此模板文件作为Hibernate配置文件,这种方式的确是快速进入Hibernate开发的方法。但是对于实际开发,通常会使用hibernate.cfg.xml文件作为配置文件。深入对比hibernate.properties和hibernate.cfg.xml文件后看如下的hibernate.properties的一个配置属性://指定数据库的方言hibernate.dialect org.hibernate.dialect.MySQLDialect上面的一行代码是典型的Properties文件的的格式,前面的key为hibernate.dialect , 后面的value是为org.hibernate.dialect.MySQLDialect。接下来我们再来查看hibernate.cfg.xml文件中的相对应的配置代码:<property name = "dialect">org.hibernate.dialect.MySQLDialect</property>同样指定了Hibernate的Dialect 属性是org.hibernate.dialect.MySQLDialect 。对比两种格式的文件,可以发现虽然格式不同但其实质完全一样。
-
Hibernate HQL 优化
初用Hibernate的人也许都遇到过性能问题,实现同一功能,用Hibernate与用JDBC性能相差十几倍很正常,如果不及早调整,很可能影响整个项目的进度。1.Hibernate性能调优大体上,对于Hibernate性能调优的主要考虑点如下:◆数据库设计调整◆HQL优化◆API的正确使用(如根据不同的业务类型选用不同的集合及查询API)◆主配置参数(日志,查询缓存,fetch_size, batch_size等)◆映射文件优化(ID生成策略,二级缓存,延迟加载,关联优化)◆一级缓存的管理◆针对二级缓存,还有许多特有的策略◆事务控制策略。2.数据库设计◆降低关联的复杂性◆尽量不使用联合主键◆ID的生成机制,不同的数据库所提供的机制并不完全一样◆适当的冗余数据,不过分追求高范式3.Hibernate HQL优化HQL如果抛开它同Hibernate本身一些缓存机制的关联,Hibernate HQL优化技巧同普通的SQL优化技巧一样,可以很容易在网上找到一些经验之谈。4.主配置查询缓存,同下面讲的缓存不太一样,它是针对HQL语句的缓存,即完全一样的语句再次执行时可以利用缓存数据。但是,查询缓存在一个交易系统(数据变更频繁,查询条件相同的机率并不大)中可能会起反作用:它会白白耗费大量的系统资源但却难以派上用场。fetch_size:同JDBC的相关参数作用类似,参数并不是越大越好,而应根据业务特征去设置batch_size:同上。生产系统中,切记要关掉SQL语句打印。
Mybatis
- Mybatis是什么
- MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis。
- MyBatis是一个优秀的持久层框架,它对jdbc的操作数据库的过程进行封装,使开发者只需要关注 SQL 本身,而不需要花费精力去处理例如注册驱动、创建connection、创建statement、手动设置参数、结果集检索等jdbc繁杂的过程代码。
- Mybatis通过xml或注解的方式将要执行的statement配置起来,并通过java对象和statement中的sql进行映射生成最终执行的sql语句,最后由mybatis框架执行sql并将结果映射成java对象并返回。
- Mybatis工作原理
- 步骤:
1. 读取核心配置文件并返回InputStream流对象。 2. 根据InputStream流对象解析出Configuration对象,然后创建SqlSessionFactory工厂对象 3. 根据一系列属性从SqlSessionFactory工厂中创建SqlSession 4. 从SqlSession中调用Executor执行数据库操作&&生成具体SQL指令 5. 对执行结果进行二次封装 6. 提交与事务
- 处理器
执行器:Executor(update, query, flushStatements, commit, rollback, getTransaction, close, isClosed),是MyBatis调度的核心,负责SQL语句的生成和查询缓存的维护 参数处理器:ParameterHandler(getParameterObject, setParameters),负责对用户传递的参数转换成JDBC Statement所对应的数据类型 结构处理器:ResultSetHandler(handleResultSets, handleOutputParameters),负责将JDBC返回的ResultSet结果集对象转换成List类型的集合 sql查询处理器:StatementHandler(prepare, parameterize, batch, update, query),封装了JDBC Statement操作,负责对JDBC statement 的操作,如设置参数等 Configuration:MyBatis所有的配置信息都保存在Configuration对象之中,配置文件中的大部分配置都会存储到该类中 SqlSession:作为MyBatis工作的主要顶层API,表示和数据库交互时的会话,完成必要数据库增删改查功能 TypeHandler:负责java数据类型和jdbc数据类型(也可以说是数据表列类型)之间的映射和转换 MappedStatement:MappedStatement维护一条<select|update|delete|insert>节点的封装 SqlSource:负责根据用户传递的parameterObject,动态地生成SQL语句,将信息封装到BoundSql对象中,并返回 BoundSql:表示动态生成的SQL语句以及相应的参数信 BaseBuilder:负责解析xml映射文件,生成sql语句,包括解析select|insert|update|delete|cache-ref|cache|resultMap|sql|parameterMap|namespace|where|trim|set|where|if|foreach|choose|if|when|otherwise|bind|parameterType|resultType|resultMap,该类是抽象类,它有很多子类。
- 步骤:
- Mybatis搭建
1、mybatis配置SqlMapConfig.xml,此文件作为mybatis的全局配置文件,配置了mybatis的运行环境等信息。mapper.xml文件即sql映射文件,文件中配置了操作数据库的sql语句。此文件需要在SqlMapConfig.xml中加载。 2、 通过mybatis环境等配置信息构造SqlSessionFactory即会话工厂 3、 由会话工厂创建sqlSession即会话,操作数据库需要通过sqlSession进行。 4、 mybatis底层自定义了Executor执行器接口操作数据库,Executor接口有两个实现,一个是基本执行器、一个是缓存执行器。 5、 Mapped Statement也是mybatis一个底层封装对象,它包装了mybatis配置信息及sql映射信息等。mapper.xml文件中一个sql对应一个Mapped Statement对象,sql的id即是Mapped statement的id。 6、 Mapped Statement对sql执行输入参数进行定义,包括HashMap、基本类型、pojo,Executor通过 Mapped Statement在执行sql前将输入的java对象映射至sql中,输入参数映射就是jdbc编程中对preparedStatement设置参数。 7、 Mapped Statement对sql执行输出结果进行定义,包括HashMap、基本类型、pojo,Executor通过 Mapped Statement在执行sql后将输出结果映射至java对象中,输出结果映射过程相当于jdbc编程中对结果的解析处理过程。
-
Mybatis常用类
SqlSession SqlSession中封装了对数据库的sql操作,如:查询、插入、更新、删除等。通过SqlSessionFactory创建SqlSession,而SqlSessionFactory是通过SqlSessionFactoryBuilder进行创建。SqlSessionFactoryBuilderSqlSessionFacoty是通过SqlSessionFactoryBuilder进行创建,SqlSessionFactoryBuilder只用于创建SqlSessionFactory,可以当成一个工具类,在使用时随时拿来使用不需要特殊处理为共享对象。SqlSessionFactorySqlSessionFactory是一个接口,接口中定义了openSession的不同方式,SqlSessionFactory一但创建后可以重复使用,实际应用时通常设计为单例模式。SqlSessionSqlSession是一个接口,默认使用DefaultSqlSession实现类,sqlSession中定义了数据库操作。执行过程如下:1、 加载数据源等配置信息Environment environment = configuration.getEnvironment();2、 创建数据库链接3、 创建事务对象4、 创建Executor,SqlSession所有操作都是通过Executor完成
- Mybatis与Hibernate不同
- Mybatis和hibernate不同,它不完全是一个ORM框架,因为MyBatis需要程序员自己编写Sql语句,不过mybatis可以通过XML或注解方式灵活配置要运行的sql语句,并将java对象和sql语句映射生成最终执行的sql,最后将sql执行的结果再映射生成java对象。
- Mybatis学习门槛低,简单易学,程序员直接编写原生态sql,可严格控制sql执行性能,灵活度高,非常适合对关系数据模型要求不高的软件开发,例如互联网软件、企业运营类软件等,因为这类软件需求变化频繁,一但需求变化要求成果输出迅速。但是灵活的前提是mybatis无法做到数据库无关性,如果需要实现支持多种数据库的软件则需要自定义多套sql映射文件,工作量大。
- Hibernate对象/关系映射能力强,数据库无关性好,对于关系模型要求高的软件(例如需求固定的定制化软件)如果用hibernate开发可以节省很多代码,提高效率。但是Hibernate的学习门槛高,要精通门槛更高,而且怎么设计O/R映射,在性能和对象模型之间如何权衡,以及怎样用好Hibernate需要具有很强的经验和能力才行。