Home ORM——对象关系映射

 

让我们从JDBC一步一步分析到ORM框架

 

ORM概念说明

一、什么是JDBC?

 

二、为什么是对象关系映射(ORM)?

当我们工作在一个面向对象的系统中时,存在一个对象模型和关系数据库不匹配的问题。RDBMSs 用表格的形式存储数据,然而像 Java 或者 C# 这样的面向对象的语言它表示一个对象关联图。考虑下面的带有构造方法和公有方法的 Java 类:

现考虑以上的对象需要被存储和索引进下面的 RDBMS 表格中:

第一个问题,如果我们开发了几页代码或应用程序后,需要修改数据库的设计怎么办?

第二个问题,在关系型数据库中加载和存储对象时我们要面临以下五个不匹配的问题。

Object-Relational Mapping (ORM) 是解决以上所有不匹配问题的方案。

 

三、什么是ORM?

对象关系映射(英语:Object Relational Mapping,简称ORM,或O/RM,或O/R mapping),是一种程序设计技术,用于实现面向对象编程语言里不同类型系统数据之间的转换。

显然,这句话说的云里雾里的,我们对其进行翻译:其主要作用是在编程中,把面向对象的概念跟数据库中表的概念对应起来。举例来说就是,我定义一个对象(实体),那就对应着一张表,这个对象的实例,就对应着表中的一条记录。

一个 ORM 系统相比于普通的 JDBC 有以下的优点:

 

 

四、Java ORM框架

在 Java 中有几个持久化的框架和 ORM 选项。一个持久化的框架是 ORM 存储和索引对象到关系型数据库的服务。

  • Enterprise JavaBeans Entity Beans
  • Java Data Objects
  • Castor
  • TopLink
  • Spring DAO
  • Hibernate
  • And many more                                            资料来源:Hibernate ORM 概览_w3cschool

 

ORM框架必备知识点

一、Hibernate的优秀之处与短板

Hibernate优点

(1) 对象/关系数据库映射(ORM)
它使用时只需要操纵对象,使开发更对象化,抛弃了数据库中心的思想,完全的面向对象思想
(2) 透明持久化(persistent)
带有持久化状态的、具有业务功能的单线程对象,此对象生存期很短。这些对象可能是普通的JavaBeans/POJO,这个对象没有实现第三方框架或者接口,唯一特殊的是他们正与(仅仅一个)Session相关联。一旦这个Session被关闭,这些对象就会脱离持久化状态,这样就可被应用程序的任何层自由使用。(例如,用作跟表示层打交道的数据传输对象。)
(3) 事务Transaction(org.hibernate.Transaction)
应用程序用来指定原子操作单元范围的对象,它是单线程的,生命周期很短。它通过抽象将应用从底层具体的JDBC、JTA以及CORBA事务隔离开。某些情况下,一个Session之内可能包含多个Transaction对象。尽管是否使用该对象是可选的,但无论是使用底层的API还是使用Transaction对象,事务边界的开启与关闭是必不可少的。
(4) 它没有侵入性,即所谓的轻量级框架
(5) 移植性会很好
(6) 缓存机制,提供一级缓存和二级缓存
(7) 简洁的HQL编程

Hibernate缺点

(1) Hibernate在批量数据处理时有弱势
(2) 针对单一对象简单的增删查改,适合于Hibernate,而对于批量的修改,删除,不适合用Hibernate,这也是OR框架的弱点;要使用数据库的特定优化机制的时候,不适合用Hibernate

 

二、Mybatis和Hibernate相比,优势在哪里

链接:https://www.zhihu.com/question/21104468/answer/92986556

测试分析

测试分成了插入,单表查询,关联查询。关联查询中hibernate分成三种情况进行配置。其中在关联字段查询中,hibernate在两种情况下,性能差异比较大。 都是在懒加载的情况下,如果推特对应的用户比较多时,则性能会比仅映射100个用户的情况要差很多。

换而言之,如果用户数量少(关联的总用户数)时,也就是会重复查询同一个用户的情况下,则不需要对用户表做太多的查询。

其中通过查询文档后,证明使用懒加载时,对象会以id为key做缓存,也就是查询了100个用户后,后续的用户信息使用了缓存,使性能有根本性的提高。甚至要比myBatis更高。

如果是关联50万用户的情况下,则hibernate需要去查询50万次用户信息,并组装这50万个用户,此时性能要比myBatis性能要差,不过差异不算大,小于1ms,表示可以接受。

其中hibernate非懒加载情况下与myBatis性能差异也是相对其他测试较大,平均值小于1ms。

这个差异的原因主要在于,myBatis加载的字段很干净,没有太多多余的字段,直接映身入关联中。反观hibernate则将整个表的字都会加载到对象中,其中还包括关联的user字段。

hibernate这种情况下有好有坏,要看具体的场景,对于管理平台,需要展现的信息较多,并发要求不高时,hibernate比较有优势。

然而在一些小活动,互联网网站,高并发情况下,hibernate的方案太不太适合,myBatis+VO则是首选。

测试总结

总体初观,myBatis在所有情况下,特别是插入与单表查询,都会微微优于hibernate。不过差异情况并不明显,可以基本忽略差异。

差异比较大的是关联查询时,hibernate为了保证POJO的数据完整性,需要将关联的数据加载,需要额外地查询更多的数据。这里hibernate并没有提供相应的灵活性。

关联时一个差异比较大的地方则是懒加载特性。其中hibernate可以特别地利用POJO完整性来进行缓存,可以在一级与二级缓存上保存对象,如果对单一个对象查询比较多的话,会有很明显的性能效益。

以后关于单对象关联时,可以通过懒加载加二级缓存的方式来提升性能。

最后,数据查询的性能与orm框架关无太大的关系,因为orm主要帮助开发人员将关系数据转化成对象型数据模型,对代码的深析上来 看,hibernate设计得比较重量级,对开发来说可以算是重新开发了一个数据库,不让开发去过多关心数据库的特性,直接在hibernate基础上进 行开发,执行上分为了sql生成,数据封装等过程,这里花了大量的时间。然而myBatis则比直接,主要是做关联与输出字段之间的一个映射。其中sql 基本是已经写好,直接做替换则可,不需要像hibernate那样去动态生成整条sql语句。

好在hibernate在这阶段已经优化得比较好,没有比myBatis在性能上差异太多,但是在开发效率上,可扩展性上相对myBatis来说好太多。

最后的最后,关于myBatis缓存,hibernate查询缓等,后续会再专门做一篇测试。

关于缓存配置

myBatis相对Hibernate 等封装较为严密的ORM 实现而言,因为hibernate对数据对象的操作实现了较为严密的封装,可以保证其作用范围内的缓存同步,而ibatis 提供的是半封闭的封装实现,因此对缓存的操作难以做到完全的自动化同步。

以上的缓存配置测试仅为性能上的分析,没有加入可用性上的情况,因为myBatis直接配置缓存的话,可能会出现脏数据,。

在关联查询数据的情况下,hiberntae的懒加载配二级缓存是个比较好的方案(无脏数据),也是与myBatis相比有比较明显的优势。此情景下,性能与myBatis持平。

在真实情况下,myBatis可能不会在这个地方上配置缓存,会出现脏数据的情况,因而很有可能在此hibernate性能会更好。

 

三、springjpa和mybatis哪个查询效率高?

 

四、Mybatis Hebernate 学习资料

Mybatis(真的超级超级详细) – 知乎 (zhihu.com)

快速入门MyBatis第一天(共两天) – 知乎 (zhihu.com)

Hibernate 教程_w3cschool

 

一、什么是JDBC?

通俗点讲,J 就是java,DB就是database,C 就是connectivity,中文意思就是java数据库连接,简单点说吧,就是通过java语言去操作数据库。原来我们操作数据库是在控制台使用sql语句来操作数据库,JDBC使用java语言向数据库发送sql语句。

二、JDBC的原理

以前有一个叫SUN的公司,他们公司的数据库工程师想开发一个操作全世界数据库的接口和规范的实现,一开工结果有点难度啊,原因是个大数据库服务器差异性太大了,于是SUN公司的负责人把这些数据库工程师叫到一起开了一个会,讨论的结果是我们不去做具体的实现了,我们定义一个接口规范,让个大数据库厂商去实现,于是各大数据库厂商按照SUN公司的规范提供了一套操作自己数据库的API,也就是JDBC接口的驱动实现类。

注:JDBC是定义的接口规范,JDBC接口的驱动实现类是由各大厂商自己提供的

三、JDBC详解

JDBC的全称是Java数据库连接(Java Database connect),它是一套用于执行SQL语句的Java API。应用程序可通过这套API连接到关系数据库,并使用SQL语句来完成对数据库中数据的查询、更新和删除等操作。应用程序使用JDBC访问数据库的方式如下图所示。

从上图可以看出,应用程序使用JDBC访问特定的数据库时,需要与不同的数据库驱动进行连接。由于不同数据库厂商提供的数据库驱动不同,因此,为了使应用程序与数据库真正建立连接,JDBC不仅需要提供访问数据库的API,还需要封装与各种数据库服务器通信的细节。为了帮助大家更好地理解应用程序如何通过JDBC访问数据库,下面通过一张图来描述JDBC的具体实现细节,如下图

preview

从上图中可以看出,JDBC的实现包括三部分。

(1)JDBC驱动管理器:负责注册特定的JDBC驱动器,主要通过java.sql. Driver Manager类实现。

(2)JDBC驱动器API由Sun公司负责制定,其中最主要的接口是java.sql. Driver接口。

(3)JDBC驱动器:它是一种数据库驱动,由数据库厂商创建,也称为JDBC驱动程序JDBC驱动器实现了JDBC驱动器API,负责与特定的数据库连接,以及处理通信细节。

2、JDBC常用API

在开发JDBC程序前,首先了解一下JDBC常用的API。JDBC API主要位于java.sql包中,该包定义了一系列访问数据库的接口和类,具体如下。

1. Driver接口

Driver接口是所有JDBC驱动程序必须实现的接口,该接口专门提供给数据库厂商使用。在编写JDBC程序时,必须要把指定数据库驱动程序或类库加载到项目的classpath中。
2. DriverManager类

Driver Manager类用于加载JDBC驱动并且创建与数据库的连接。在Driver Manager类中,定义了两个比较重要的静态方法。如表所示:

registerDriver(Driver driver) 

该方法用于向 DriverManager中注册给定的JDBC驱动程程序

getConnection(String url,String user,String pwd)

该方法用于建立和数据库的连接,并返回表示连接的 Connection对象

3、Connection接口

Connection接口代表Java程序和数据库的连接,在Connection接口中,定义了一系列方法,具体如表所示。

getMetaData()
该方法用于返回表示数据库的元数据的 DatabaseMetaData对象

createStatement()
用于创建一个Statement对象来将SQL语句发送到数据库

prepareStatement(String sql)
用于创建一个PreparedStatement对象来将参数化的SQL语句发送到数据库

prepareCall(String sql)
用于创建一个CallableStatement对象来调用数据库存储过程

4、Statement接口

Statement接口用于向数据库发送SQL语句,在Statement接口中,提供了三个执行SQL语句的方法,具体如表所示。

execute(String sql)

用于执行各种SQL语句,该方法返回一个boolean类型的值,如果为true,表示所执行的SQL语句具备查询结果,可通过Statement的getResultSet方法获得查询结果。

executeUpdate(String sql)

用于执行SQL中的Insert、update和delete语句。该方法返回一个int类型的值,表示数据库中受该SQL语句影响的记录的数目。

executeQuery(String sql)

用于执行SQL中的select语句,该方法返回一个表示查询结果的ResultSet对象

5. PreparedStatement接口

PreparedStatement是Statement的子接口,用于执行预编译的SQL语句。在PreparedStatement接口中,提供了一些基本操作的方法,具体如表下所示。

executeUpdate()

在此PreparedStatement对象中执行SQL语句,该语句必须是个DML语句或者是无返回内容的SQL语句,比如DDL语句。

executeQuery()

在此PreparedStatement对象中执行SQL查询,该方法返回的ResultSet对象

setInt(int parameterIndex, int x)

将指定参数设置为给定的int值

setFloat(int parameterIndex, float x)

指定参数设置为给定的float值

setString(int parameterIndex, String x)

将指定参数设置为给定的String值

setDate(int parameterIndex, Date x)

将指定参数设置为给定的Date值

addBatch()

将一组参数添加到此PreparedStatement对象的批处理命令中

setCharacterStream(parameterIndex, reader, length)

将指定的输入流写入数据库的文本字段

setBinaryStream(parameterIndex, x, length)

将二进制的输入流数据写入到二进制字段中

需要注意的是,上表中的setDate()方法可以设置日期内容,但参数Date的类型是java.sq.Date,而不是java.util.Date。

6、CallableStatement接口

CallableStatement是PreparedStatement的子接口,用于执行SQL存储过程。在Callablestatement按接口中,提供了一些基本操作的方法,具体下表所示:

registerOutParameter(int parameterIndex,int sqlType)

按顺序位置将OUT参数注册为SQL类型。其中,parameterIndex表示顺序位置,sqlType表示SQL类型

setNull(String parameter Name, int sqlType)

将指定参数设置为SQL类型的NULL

setString(String parameterName, String x)

查询最后一个读取的OUT参数是否为SQL类型的NULL

wasNull()

查询最后一个读取的OUT参数是否为SQL类型的NULL

getlnt(int parameterIndex)

以Java语言中int值的形式获取指定的数据库中INTEGER类型参数的值

需要注意的是,由于 CallableStatement接口继承PreparedStatement,PreparedStatement接口又继承了 Statement,因此CallableStatement接口中除了拥有自己特有的方法,也同时拥有了这两个父接口中的方法。

7、ResultSet接口

ResultSet接口表示 select查询语句得到的结果集,该结果集封装在一个逻辑表格中。在 ResultSet接口内部有一个指向表格数据行的游标,ResultSet对象初始化时,游标在表格的第一行之前。下表中列举了ResultSet接口中的常用方法。

getString(int columnIndex)

用于获取指定字段的String类型的值,参数columnIndex代表字段的索引
getString(String columnName)

用于获取指定字段的String类型的值,参数column Name代表字段的名称
getInt(int columnIndex)

用于获取指定字段的int类型的值,参数columnIndex代表字段的索引

getInt(String columnName)

用于获取指定字段的int类型的值,参数columnName代表字段的名称

getDate(int columnIndex)

用于获取指定字段的Date类型的值,参数columnIndex代表字段的索引
getDate(String columnName)

用于获取指定字段的Date类型的值,参数column Name代表字段的名称
next()

将游标从当前位置向下移一行
absolute(int row)

将游标移动到此Resultset对象的指定行
afterLast()

将游标移动到此ResultSet对象的末尾,即最后一行之后
beforeFirst()

将游标移动到此Resultset对象的开头,即第一行之前
previous()
将游标移动到此ResultSet对象的上一行
last()

将游标移动到此ResultSet对象的最

从上表中可以看出,ResultSet接口中定义了大量的getXxx()方法,采用哪种getXxx()方法取决于字段的数据类型。程序既可以通过字段的名称来获取指定数据,也可以通过字段的索引来获取指定的数据,字段的索引是从1开始编号的。

四、JDBC实现步骤

DBC编程大致按照以下几个步骤进行。

(1) 加载并注册数据库驱动,具体方式如下。

DriverManager.registerDriver(Driver driver);

(2) 通过Driver Manager获取数据库连接,具体方式如下。

Connection conn= DriverManager.getConnection(String url, String user, String pass);
从上述方式可以看出,getConnection()方法中有三个参数,它们分别表示数据库url、登录数据库的用户名和密码。数据库山通常遵循如下形式的写法。

jdbc:subprotocol:subname

上面的URL写法中jdbc部分是固定的,subprotocol指定链接达到特定数据库的驱动程序,而subname部分则很不固定,也没有什么规律,不同数据库的形式可能存在较大差异,一Mysql数据库为例,其形式如下:

jdbc:mysql://hostname:port/databasename

(3)通过Connection对象获取Statement对象。Connection创建Statement的方式有如下三种。

① createStatement(): 创建基本的Statement对象

② prepareStatement(): 创建PreparedStatement对象。

③ preparCall(): 创建CallableStatement对象。

以创建基本的Statement对象为例,具体方式如下。

Statement stmt=conn.createStatement();

(4)使用Statement执行SQL语句。所有的Statement都有如下三种方法来执行语句。

①execute():可以执行任何SQL语句。

②executeQuery():通常执行查询语句,执行后返回代表结果集的Resultset对象。

③executeUpdate():主要用于执行DML和DDL语句。执行DML语句,如INSERT、UPDATE或 DELETE时,返回受SQL语句影响的行数,执行DDL语句返回0。

以executeQuer()方法为例,具体方式如下。

//执行SQL语句,获取结果集ResulSet
ResultSet rs=stmt.executQuery(sql);

(5)操作ResultSet结果集。如果执行的SQL语句是查询语句,执行结果将返回Resultset对象,该对象里保存了SQL语句查询的结果。程序可以通过操作该ResultSet对象来取出查询结果。 ResultSet对象提供的方法主要可以分为以下两类。

①next()、previous()、first()、last()、beforeFirst()、afterLast()、absolute()等移动记录指针的方法

②getXxx()获取指针指向行,特定列的值。

(6)回收数据库资源。关闭数据库连接,释放资源,包括关闭ResultSet、Statement和Connection等资源。

五、JDBC实现案例

1、搭建实验环境

CREATE DATABASE chapter01;
USE chapter01;
CREATE TABLE users(
      id INT PRIMARY KEY AUTO_INCREMENT,
      name VARCHAR(40),
      password VARCHAR(40),
      email VARCHAR(60),
      birthday DATE
)CHARACTER SET utf8 COOLLATE utf8_genneral_ci;

数据库和表创建成功后,再向users表中插入三条数据,SQL语句如下所示。

INSERT INTO users(NAME,PASSWORD,email,birthday)
VALUES('zs','123456','zs@sina.com','1980-12-04');
INSERT INTO users(NAME,PASSWORD,email,birthday)
VALUES('lisi',123456,1isi@sina.com,'1981-12-04');
INSERT INTO users(NAME,PASSWORD,email,birthday)
VALUES('wangwu',123456,'wangwu@sina.com','1979-12-04');

2、导入数据库驱动

新建Java工程chapter01,将要访问的数据库驱动文件添加到classpath中。由于应用程序访问的是MySQL数据库,因此,将MySQL的数据库驱动文件mysql-connector-java-5.0.8-bin.jar添加到classpath中即可。

3、编写JDBC程序

在工程chapter01中,新建Java类Example01,该类用于读取数据库中的users表,并将结果输出,如例下面案例所示。

package cn.itcast.jdbc.example;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Date;
public class Example01 {
    public static void main(String[] args) throws SQLException {
        //1.注册数据库的驱动
        DriverManager.registerDriver(new com.mysql.jdbc.Driver());
        //2.通过 DriverManager获取数据库连接
        String url="jdbc:mysql://localhost:3306/chapter01";
        String usernames="root";
        String password="itcast";
        Connection conn=DriverManager.getConnection(url, username, password);
        //3.通过 Connection对象获取 Statement对象
        Statement stmt= conn.createStatement();
        //4.使用 Statement执行SQL语句
        String sql="select * from users";
        ResultSet rs=stmt.executeQuery(sql);
        //5、操作 ResultSet结果集
        System.out.println("id|name|password|email|birthday");
        while (rs.next()) {
            int id=rs.getInt("id");     //通过列名获取指定字段的值
            String name=rs.getString("name");
            String psw=rs.getString("password");
            String email=rs.getString("email");
            Date birthday=rs.getDate("birthday");
            System.out.println(id+"|"+name+"|"+psw+"|"+email+"|"+birthday); 
        }
        //6.回收数据库
        rs.close();
        stmt.close();
        conn.close();
    }
}

程序执行后,会讲从users表中读取到的数据打印到控制台。

在上面案例中演示了JDBC访问数据库的步骤。首先注册MySQL的数据库驱动器类,通过 DriverManager获取一个Connection对象,然后使用Connection对象创建了一个Statement对象,Statement对象能够通过executeQuery()方法执行SQL语句,并返回结果集ResultSet对象。最后,通过遍历Resultset对象便可得到最终的查询结果。需要注意的是,在实现第一个JDBC程序时,还有两个方面需要改进,具体如下。

六、加餐

1、注册驱动

在注册数据库驱动时,虽然DriverManager.registerDriver(new com. mysql.jdbc.Driver())方法可以完成,但会使数据库驱动被注册两次。这是因为Driver类的源码中,已经在静态代码块中完成了数据库驱动的注册。所以,为了避免数据库驱动被重复注册,只需要在程序中加载驱动类即可,具体加载方式如下所示。

Class.forName("com.mysqk.jdbc.Driver");

Class.forName() 方法

此方法含义是:加载参数指定的类,并且初始化它。

Java class.forname 详解 | 菜鸟教程 (runoob.com)

2、释放资源

由于数据库资源非常宝贵,数据库允许的并发访问连接数量有限,因此,当数据库资源使用完毕后,一定要记得释放资源。为了保证资源的释放,在Java程序中,应该将最终必须要执行的操作放在finally代码块中,具体方式如下。

if(rs!=null) {
    try {
        rs.close();
    }catch (SQLException e) {
        e.printStackTrace();
    }
    rs=null;
}
if(stmt!=null) {
    try {
        stmt.close();
    }catch (SQLException e) {
        e.printStackTrace();
    }
    stmt=null;
}
if(conn!=null) {
    try {
        conn.close();
    } catch (SQLException e) {
        e.printStackTrace();
    }
    conn=null;
}

文章转载链接:什么是JDBC,JDBC的原理是什么 - 简书 (jianshu.com)
什么是JDBC?这篇文章告诉你 - 知乎 (zhihu.com)