【JAVA概述(二)】之与数据库的交互

往期
【JAVA概述(一)】



前言

在往期【JAVA概述(一)】中我们提到过围绕java出现的一系列技术都是源自交互,本期就针对与数据库的交互进行简单描述


一、JDBC

1、简介

是使用Java语言操作关系型数据库的一套API;全称:( Java DataBase Connectivity ) Java 数据库连接。
JAVA提供接口,各个数据库产商提供实现(驱动)。因此我们在同一接口的基础上可以通过替换驱动灵活切换数据库。
记住,技术发展的一个主要方向就是在业务不断复杂的趋势下让编码和维护更容易(至少不应该变得更复杂)。

2、分析

2.1、基本对象

2.1.1、DriverManager(驱动管理类)
  • 注册驱动DriverManager.registerDriver(new Driver());
    在数据库产商提供的驱动中,每个驱动都会有一段静态代码
    static{
    	try{
    		DriverManager.registerDriver(new Driver());
    	} catch (SQLException var1){
    		throw new RuntimeException("Can't register driver!");
    	}
    }
    
    根据类加载的条件,在调用反射时触发类加载,此时静态代码块会执行。因此使用Class.forName("com.mysql.jdbc.Driver");即可完成注册的驱动
  • 获取数据库连接DriverManager.getConnection(url, username,password);
    返回数据库连接对象Connection
2.1.2、Connection(数据库连接对象)
  • 获取执行 SQL 的对象 Connection.createStatement()
    返回普通执行SQL对象Statement
  • 管理数据库事务
    开头提到JDBC是是使用Java语言操作关系型数据库的一套API,既然如此,自然需要支持使用JAVA进行数据库的事务控制
    conn.setAutoCommit(false);//设置手动提交事务,默认为true,自动提交事务
    conn.commit();
    conn.rollback();
    
    因为是通过JAVA来操作关系型数据库,因此需要考虑代码可靠性。我们需要加入异常管理来保证事务的完整性。如下:
    try{
    	开启事务
    	事务逻辑
    	关闭事务
    }catch(xxx){
    	事务回滚
    }
    
2.1.3、Statement (普通执行SQL对象)

用来执行SQL语句的对象。而针对不同类型的SQL语句使用的方法也不一样。

*、执行DDL(定义数据结构)、DML(操作数据内容)语句:
executeUpdate(String sql);可能返回内容,也可能不返回内容
*、执行DQL(数据查询)语句:
executeQuery(String sql);返回结果集对象ResultSet
2.1.4、ResultSet(结果集对象)

封装了SQL查询语句的结果并提供了操作查询结果数据的方法

boolean next():将光标从当前位置向前移动一行,并返回当前行是否为有效行
	方法返回值说明:
		true : 有效航,当前行有数据
		false : 无效行,当前行没有数据
getXxx(参数):获取xxx类型的数据
	Xxx : 数据类型;如: int getInt(参数) ;String getString(参数)
	参数:
		int类型的参数:列字段的编号,从1开始
		String类型的参数: 列字段的名称

2.2、基本使用流程与分析

通常初学时,我们会走以下流程(以连接MySQL为例):

1、创建工程,导入驱动jar包:mysql-connector-java-版本号-jar
2、注册驱动:Class.forName("com.mysql.jdbc.Driver");
3、获取连接:Connection conn =DriverManager.getConnection(url, username,password);
4、获取执行sql的对象:Statement stmt = conn.createStatement();
5、定义sql语句:String sql = “select ...” ;
6、执行sql:stmt.executeUpdate(sql);
7、处理DQL(查询)的返回的数据(DML\DDL语句返回数字,可以通过值来判断是否成功)
8、释放本次操作占用的资源

观察上面的操作,我们简单对上面的流程进行一下直观评价

第一印象:过程有点长,那这意味着什么?
				1、编写繁杂; 2、底层执行效率不高;
				(编写简单的底层不一定执行简单(语法糖),编写复杂的底层一定不简单)
然后是:这个执行sql的对象能多次执行sql吗,不能的话每次执行一个sql都要走这么一大段,未免重复率太高了吧?
再就是:这个获取连接的效率如何,要是不高的话,每次都在要用时耗费大量时间连接,体验是不是很差?

直观感受就是上面那些,总结一下就是

1、站在编码者角度:编写和维护是否麻烦(代码冗长、代码重复,不好修改)
2、站在用户角度:体验是否良好(运行效率带来的响应问题)

几百年的人生经验告诉我,直觉都是有局限的,随着应用的不断深入和技术的发展,基于当时编码理念来看完美的代码可能会出现一些意料之外的问题(出现安全漏洞等)

综合上面的分析,给出进一步讨论JDBC的基调(按照重要程度排序)

1、站在安全角度:是否有安全隐患
2、站在用户角度:体验是否良好(运行效率带来的响应问题)
3、站在编码者角度:编写和维护是否麻烦

2.3、JDBC自身的解决方案

从上一小结,我们应该是清楚了接下来的讨论应该往哪个方向发展了。实话说,JDBC作为一个成熟的产物,对于我们上面提到的三点肯定是有应对方案了。因此,本节介绍JDBC的解决方案

2.3.1、如何处理出现的安全隐患

首先说说有什么安全隐患,那就是SQL注入。
SQL注入是通过操作输入来修改事先定义好的SQL语句,用以达到执行代码对服务器进行攻击的方法。

最常见的一个例子就是SQL语句为select * from tb_user where username = A and password = B
此时A取值为数据库对应字段任意一个值;B取值为'' or '1' ='1',可以看出,无论如何都能成功获取字段A对应的那条数据。
SQL注入的核心思想就是将参数作为SQL语句逻辑的一部分,而并非一个字面量,进而影响整个SQL原本的判断逻辑
那么JDBC是怎么解决这种安全隐患的呢?
我们从SQL注入的核心思想入手,既然SQL注入是通过使用参数偷梁换柱将原本SQL逻辑修改;那我们干脆让传入的参数不具备这种能力即可。

PreparedStatement对象
	通过Connection.prepareStatement(sql)获取
	传入PreparedStatement的敏感参数均会被转义,如' or '1' ='1 >>> '\'or \'1\' = \'1
	被转义的字符自然无法再被当作SQL逻辑的一部分
2.3.2、如何优化用户体验

如何优化用户体验?
注意现在是在讨论JDBC,我们的重心应该放在如何提高数据库的响应速度
好,那怎么提高?
两步走:1、减少获取连接的时间;2、减少一次连接调用数据库的时间;

2.3.2.1、减少获取连接时间

小故事
通常,一家单位(线程池)会雇佣固定人数的正式员工(常驻线程数),这些员工负责接收来自外部的各种需求。突然有一天,单位陆续来了一群客户,客户数量大于员工数量,而此时员工手上都有各自的事。这时,单位会去招一批临时工来应急(最大线程数)。结果这时,又来了一大批客户,正式员工和临时工手上都有事,怎么办?答案是没法子,叫经理(线程池策略)让客户看着办吧。你问我为什么不再招临时工?拜托阿sir,工位就那么几个,都被正式工和前面的临时工坐满了。再招就没地方待了(内存不足)

上面的故事介绍了一个普通线程池的日常。线程池不是我们这次讨论的重点,但是值得注意的是这个线程池所反映出来的一个设计思路:池化技术

池化技术:
提前保存大量的资源,以备不时之需以及重复使用。池化技术应用广泛,如内存池,线程池,连接池等等。
这是由于在实际应用当做,分配内存、创建进程、线程都会设计到一些系统调用,
系统调用需要导致程序从用户态切换到内核态,是非常耗时的操作。
因此,当程序中需要频繁的进行内存申请释放,进程、线程创建销毁等操作时,
通常会使用内存池、进程池、线程池技术来提升程序的性能。

想想也是,正常想开展长期稳定的业务时,会出现用完一个裁一个,要用再招的情形吗?(有的话请喊我去看热闹)
池化技术落地到数据库上便是连接池技术

数据库连接池是个容器,负责分配、管理数据库连接(Connection)
它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个;
释放空闲时间超过最大空闲时间的数据库连接来避免因为没有释放数据库连接而引起的数据库连接遗漏
好处
	资源重用
	提升系统响应速度
	避免数据库连接遗漏

官方(SUN) 提供的数据库连接池标准接口DataSource,由第三方组织实现此接口。该接口提供了获取连接的功能
(从配置文件里需要指定驱动来看,这个是基于各个数据库产商的驱动做的基于SUN接口和驱动之间的一层整合与管理)
常见的数据库连接池:DBCP、C3P0、Druid(下面介绍这个的基本内容)

从介绍上看,有了连接池,我们可以不必去关心连接的事情了;但是我们得给连接池下指令,指导连接池如何去管理这些连接。

Druid(德鲁伊)
	介绍
		Druid连接池是阿里巴巴开源的数据库连接池项目,功能强大,性能优秀,是Java语言最好的数据库连接池之一
	使用
		导入jar包 druid-1.1.12.jar
		定义配置文件(创建一个配置文件,假设就叫做A.properties)
			常用配置项
					driverClassName:指定驱动
					url:数据库地址
					username:用户名
					password:密码
					initialSize:初始连接数
					maxActive:最大连接数
					maxWait:连接超时最大等待时间
		加载配置文件
					Properties prop = new Properties();
        			prop.load(new FileInputStream("项目内路径/A.properties"));
		获取数据库连接池对象
					DruidDataSourceFactory.createDataSource(prop);返回连接池对象DataSource
					(public class DruidDataSourceFactory extends Object implements ObjectFactory:
					用的是jar包里自己的类感觉这里的设计有点个性化了,
					私以为如果用SUN提供的接口获取对象合理一点,不然以后要是换连接池还得改代码。)
		获取连接
					DataSource.getConnection(),返回连接对象Connection

有了连接池,可以大大减少获取连接的时间。因为大部分时间都是只要去池子里面取一个连接就好。

2.3.2.2、减少一次连接调用数据库的时间

在安全问题那里,我们对sql的执行已经确定了需要用PreparedStatement对象,这个对象不止可以防止SQL注入,还可以实现预编译

在url后添加键值对useServerPrepStmts=true,可以让PreparedStatement对象开启预编译功能
示例: String url = "jdbc:mysql:///db1?useSSL=false&useServerPrepStmts=true";

以MYSQL为例,Java代码操作数据库流程如图所示:
	将sql语句发送到MySQL服务器端,MySQL服务端会对sql语句进行如下操作
		1、检查SQL语句的语法是否正确。
		2、编译SQL语句。将SQL语句编译成可执行的函数。
		3、执行SQL语句
检查SQL和编译SQL花费的时间比执行SQL的时间还要长。
如果我们只是重新设置参数,那么检查SQL语句和编译SQL语句将不需要重复执行。这样就提高了性能。

简单说:开启预编译后,数据库对一次连接期间对同一个PreparedStatement对象执行的两次sql不会再对sql进行操作1和操作2了,因为sql已经预编译过了,后续就只会直接传参执行
在这里插入图片描述

2.3.3、如何解决编写和维护麻烦的问题

感觉这块JDBC自身没有什么好的方案(有的话麻烦和我说说)

2.3.4、结论

JDBC自身处理针对安全隐患和用户体验给出了自己的解决方案,效果也是比较让人满意。但美中不足的是不体谅开发的艰辛(泪)。
直观上,后续介绍的上位技术的倾向应该是比较明显了(优先级从高到低)
1、降低开发的工作量、降低维护的难度
2、提高用户体验、在解决现有的安全问题的前提下处理别的安全问题(如果有的话)

二、Mybatis

Mybatis是一个半自动ORM框架,可以为开发者提供较JDBC更加友好的环境。它不仅为开发和运维提供了很大的方便,同样对性能有所提升。

1、介绍准备

1.1、ORM介绍

ORM ( Object Relational Mapping)即 “对象关系映射”
顾名思义,ORM旨在将JAVA对象与数据库结构建立起映射关系
这种关系是双向的,1、既要实现JAVA对象往数据库里传递数据;2、也要实现数据库以对象的形式输出数据

之所以会有这种思路,主要是JAVA的特点是面向对象编程;使用面向对象编程时,数据很多时候都存储在对象里面,具体来说是存储在对象的各个属性(也称成员变量)中。
按照传统思路,持久化数据时,就得手动编写 SQL 语句,将对象的属性值提取到 SQL 语句中,然后再调用相关方法执行 SQL 语句;反过来,从数据库取数据时也是基本一样的工作量。按照这种思路,假如对象一多,对于开发和维护来说,无疑是灾难。
而按照ORM的思路,只要提前配置好对象和数据库之间的映射关系,ORM 就可以自动生成 SQL 语句,并将对象中的数据自动存储到数据库中或将数据库数据提取到对象中,整个过程不需要人工干预。
在 JAVA 中,ORM的落地技术 一般使用 XML 或者注解来配置对象和数据库之间的映射关系。

1.2、ORM落地–Hibernate简介

1.2.1、引子

Hibernate是基于 ORM 理念的 Java 持久化框架。
框架即为某类方向的实现所提供的可拓展解决方案(像是房子的基础架构决定了一些它的根本特性,比如是圆顶还是平顶;但我们可以在这个基础上按照自己的喜好布局上自己的风格(颇有种大事集权小事放权的意味));
所谓持久化框架,就是提供连接和操作数据库的JAVA框架。JDBC本身就是持久化框架,因为它为连接和操作数据库这个目的提供了一整套解决方案,并且可以拓展比如使用连接池优化等)
基于ORM理念也就意味着这个持久化框架具备能够直接将数据库数据与对象进行相互的直接转化。
后续我们将基于ORM的持久化框架简称为ORM框架

  • Hibernate支持市面上主流的数据库
  • bernate是一种全自动的ORM框架
    • 这里全自动的含义是具备双向的映射关系。全自动ORM听上去就是个病句,因为ORM的理念就是建立双向的映射关系,但是之所以这么说,是因为有的框架因为自身的一些理念,并未完整落实ORM的理念,因此落下个半自动ORM的说法。全自动ORM就是为了区别开半自动ORM以免引起混淆的说法。
  • 优势
    • 1、落地了ORM理念,简化了操作
    • 2、提供了缓存等一系列措施,提高了性能
1.2.2、介绍
1.2.2.1、核心接口
  • Configuration
    • 只存在于系统的初始化阶段,将 SessionFactory 创建完成后,就会自动销毁,无需手动销毁
    • 用法
      • 创建 Configuration 实例
        调用Configuration configuration = new Configuration().configure();
        加载核心配置文件 hibernate.cfg.xml
        当核心配置文件在src 目录下时,可以无参,否则需要在configure()中传入相对路径
        
      • 加载映射文件
        例子:加载User类对应的映射文件
        通常方法:
        Configuration.addResource("net/biancheng/www/mapping/User.hbm.xml");
        特殊方法:当映射文件要完全按照“实体类.hbm.xml ”的形式命名(User.hbm.xml);并且映射文件与实体类在同一个包下时,可以用下面的方法加载映射文件
        Configuration.addClass(User.class);
        
  • SessionFactory

    SessionFactory sessionFactory = configuration.buildSessionFactory();

    • 用来读取和解析映射文件,并负责创建和管理 Session 对象
    • 维护了应用级别的数据信息包括:
      • 当前的数据库配置信息、所有映射关系以及 Hibernate 自动生成的预定义 SQL 语句
      • Hibernate 的二级缓存(一个可配置的缓存插件)
    • * 应用级别也就意味着这里面维护的数据是被所有会话(Session)共享的,同时,维护这么大体量的数据也意味着这是一个重量级对象
    • 一个 SessionFactory 实例对应一个数据库存储源,Hibernate 应用可以从 SessionFactory 实例中获取 Session 实例。
    • 特点
      • 线程安全,它的同一个实例可以被应用多个不同的线程共享
      • 重量级,并且对应一个数据库存储源,因此如果只访问一个数据库的话只需要维护一个这样的实例即可
      • 支持多实例,当有需要访问多个数据库的需求,则可以同时维护多个实例
  • Session

    两种获取Session对象的方式:
    1、SessionFactory 直接创建一个新的 Session 实例,且在使用完成后需要调用 close() 方法进行手动关闭。
    Session session = sessionFactory.openSession();
    2、SessionFactory创建的 Session 实例会被绑定到当前线程中,它在事务提交或回滚后会自动关闭这意味着会话的生命周期与缩短为与事务一致
    Session session = sessionFactory.getCurrentSession();

    • 用于应用与数据库进行交互,被称为Hibernate 的持久化管理器。
      • 所有持久化对象必须在 Session 的管理下才可以进行持久化操作,持久化类只有与 Session 关联起来后,才具有了持久化的能力。
      • 担任的一部分职责对标JDBC的Connection对象(连接数据库、数据库的事务与基本操作)
    • 维护Hibernate 的一级缓存(包括一切基于这个Session对象对数据库数据的保存、更新、删除和查询)
    • 特点
      • 不是线程安全的,而且由于只维护本会话的数据,因此是轻量级的,这意味着我们可以并且应该进行频繁的创建与销毁
    • 持久化方法
      save()	执行插入操作
      update()	执行修改操作
      saveOrUpdate()	根据参数,执行插入或修改操作
      delete()	执行删除操作
      get()	根据主键查询数据(立即加载)
      load()	根据主键查询数据(延迟加载)
      createQuery()	获取 Hibernate 查询对象
      createSQLQuery()	获取 SQL 查询对象
      
    • 其他方法
      flush()  刷出缓存
      close()  关闭会话
      
  • Transaction

    两种获取Transaction的方式:
    1、根据 Session 获得一个 Transaction 对象,但是并没有开启事务,需要手动继续调用 Transaction 的 begin() 方法开启事务。
    Transaction transaction = session.beginTransaction();
    2、根据 Session 获得一个 Transaction 对象,自动继续调用 Transaction 的 begin() 方法开启事务。
    Transaction transaction1 = session.getTransaction();

    • 数据库事务管理接口,它对底层的事务接口进行了封装
    • 事务管理方法
      begin()	该方法用于开启事务
      commit() 	该方法用于提交事务
      rollback() 	该方法用于回滚事务
      
  • Query
    • 很像PrepareStatement对象的用法,但是不支持insert操作
    • 具体的sql操作懒得写了,要用的时候百度就好
1.2.2.2、Hibernate缓存

Hibernate 提供了两种缓存机制:一级缓存和二级缓存

  • 一级缓存
    - 基于Session对象,因此是会话级别的缓存
  • 二级缓存
    - 基于SessionFactory对象,因此是应用级别的缓存
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值