1、JDBC 介绍
Java DataBase Connectivity Java连接数据库操作一种技术!JDBC是一套连接数据库 操作数据库的标准!JDBC就是Java提供接口规范(java.sql),其他的数据库厂商写实现!
优势:
- 统一数据库操作的语法
- 降低学习曲线
- 可以无感切换数据库!
2、JDBC常用的类和接口
DriverManager 类 加载驱动 注册驱动
//注册驱动 DriverManager.registerDriver(new Driver());
//获取连接 DriverManager.getConnection(url, user_name , password); //url 统一资源定位符:jdbc:mysql://localhost:3306/库名。 //user_name :数据库的用户名; //password : 数据库的密码
Driver 接口 驱动的标准类
- 该接口来自com.mysql.jdbc包下,是导入的jar包里面的
Connection 接口 用于建立数据库和java程序之间连接
//获取statement,用于执行SQL语句 并获取返回结果的对象 connection.createStatement();
Statement 接口 用于执行SQL语句 并获取返回结果的对象
执行DML
//返回一个long类型的值,他代表受影响的行数,通常用于逻辑判断,或者作为方法返回 long result = statement.executeUpdate(sql);
执行DQL
//返回一个result结果集,依次获取 ResultSet resultSet = statement.executeQuery(sql);
Resultset 接口 返回的结果(虚拟的表对象化)
- 如何对ResultSet做遍历:
- 利用next() 方法判断,看是否有下一个元素
- 利用getXXX(xxx)获取值
- XXX代表返回值类型 ,例如String,int
- xxx代表 列名 或者 下标 注意这个下标是从1开始的
3、JDBC的基本使用
步骤分为八步:
- 导入第三方的jar包
- 项目根路径下创建一个libs包
- 复制jar包到此文件夹
- jar右键 - as a lib
- jar前面多了打开的三角号
- 注册驱动
- 获取连接
- 创建执行的SQL语句的statement
- statement执行SQL语句
- 获取结果集
- 解析结果集
- 关闭资源
//注册驱动 DriverManager.registerDriver(new Driver()); //创建连接 Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/库名", "root", "root"); //创建statement Statement statement = connection.createStatement(); //创建sql语句 String sql = "INSERT INTO users SET u_name ='" +name1+"', u_password = '" + password1 +"'"; //执行sql语句,获取结果 没有预编译,会出现sql注入问题 long result = statement.executeUpdate(sql); //处理结果 if (result != 0 ){ return true ; } //释放资源 if(resultSet != null) { resultSet.close(); } if(statement != null){ statement.close(); } if(connection != null){ connection.close(); } return false ;
4、JDBC涉及到的类及接口详解
4.1 注册驱动
有两种方法
- Cless.forName(“com.mysql.jdbc.Driver”); 触发类加载,不用接收
- DriverManager.registerDriver(new Driver());
- Driver 来自包 com.mysql.jdbc
- com.mysql,jdbc.Driver implements java.sql.Driver
DriverManager
用于管理一组JDBC驱动程序的基本服务。
public static void registerDriver(Driver driver)
注册指定数据库厂商的驱动!driver就第三方的驱动!(com.mysql.jdbc)
不会反复注册!
两种驱动方法加载的区别:
在com.mysql.jdbc.Driver中有一段静态代码块,
public class Driver extends NonRegisteringDriver implements java.sql.Driver { static { try { java.sql.DriverManager.registerDriver(new Driver()); } catch (SQLException E) { throw new RuntimeException("Can't register driver!"); } }
他向 DriverManager注册一个Driver实例。这样在Class.forName(“com.mysql.jdbc.Driver”)的时候,就会首先去执行这个静态代码块,于是和DriverManager.registerDriver(new Driver())有了相同的效果。
因此,反射只会调用一次驱动加载,而registerDriver方法会调用加载方法,注意,两种方法都只会注册一次驱动,推荐适用Class.forName();
4.2获取Connection 连接
DriverManager.getConnection(url , user , password);
获取指定数据的连接(会话)、可以进行数据库事务操作、获取操作执行数据的Statment对象!
其中url可以为 :
- jdbc:mysql:///库名
- jdbc:mysql://localhost:3030/库名
- jdbc:mysql://107.0.0.1:3030/库名
getConnection( )参数的三种形式
getConnection(url , user ,password)
getConnection(url , properties)
- properties和Map集合类似,他的key-value 都是字符串类型
- properties.setProperties(“user” , “root”);
- properties.setProperties(“password” , “123”);
getConnection(url)
这个url比较厉害,终究是他一个人默默的扛下了所有
url = "jdbc:mysql:///库名 ?user = root & password = 123 "
常用的方法:
- connection.createStatement(); //静态的sql语句 ,没有条件的sql语句,有注入攻击
- connection.preparedStatement(); //动态的sql语句 , 有条件的sql语句,可以避免sql注入
- connection.close();
4.3statement
用来执行sql语句的工具类
PreparedStatement接口继承了Statement接口,可以通过预编译sql语句,避免sql注入的问题
String sql = "select * from user where id = ? and name = ?" //预编译sql语句, ? 代表占位 PreparedStatement statement = connection.preparedStatement(sql); //在执行executeQuery 之前,需要先给 ?赋值 //setObject(int parameterIndex , Object o) // parameterIndex代表着第几个 ? o 代表值 statement.setObject(1,id) ; statement.setObject(2,name) ;
常用的方法:
- execute(); 可执行的sql语句,五类都中
- 返回结果:dql时返回true ,其他类型返会false
- 适用场景:
- 当你不知道sql语句的类型的时候;
- 没有结果的:dcl tpl ddl
- executeUpdate() :执行DML
- 返回结果: int类型的受影响的行数
- 适用场景:执行DML
- executeQuery():执行DQL
- 返回结果:ResultSet :永远不为null 的虚拟对象,没有数据则返回列名。
- 适用场景:执行DQL
- close();
4.4 resultset
移动光标:光标在刚开始的时候,位于数据第一行之前,不在数据上
- next():向下移动光标,并且返回是否存在下一行!
- first(): 光标移动到数据的第一行
- last():光标移动到数据的最后一行
获取列的数据:getXXX(xxx) :xxx,有两种类型int columnIndex 列的顺序排名 String columnLabel 列名
查询表的信息
利用方法getMetaDate() 返回列的所有信息,即虚拟表的列的信息对象!注意一个metadata对应一个resultset对象!metadata 相当于列名,对应实体对象的属性名。resultset对象对应值。
使用方法:
MetaData metaDate = resultset.getMetaData() //列数可以用来判断循环赋值的次数 metaDate.columnCount() 返回列数 //注意:Columnlabel 和 ColumnName 的区别 metaDate.getColumnlabel() 返回虚拟表的列名 metaDate.getColumnName() 返回数据源表的列名 //例子: public static <T>T executeQuery(String sql ,Class<T> c, Object... params) throws Exception{ T t = c.newInstance(); Connection connection = getConnection(); PreparedStatement preparedStatement = connection.prepareStatement(sql); if (params.length >= 1){ for (int i = 0 ; i < params.length ; i++) { preparedStatement.setObject(i+1,params[i]); } } ResultSet resultSet = preparedStatement.executeQuery(); ResultSetMetaData metaData = resultSet.getMetaData(); //列数 int columnCount = metaData.getColumnCount(); while (resultSet.next()){ for (int i = 1; i <= columnCount; i++) { String columnLabel = metaData.getColumnLabel(i); Object object = resultSet.getObject(i); PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnLabel, c); Method writeMethod = propertyDescriptor.getWriteMethod(); writeMethod.invoke(t , object) ; } } close(connection ,preparedStatement , resultSet); return t ; }
属性描述器(内部就是反射: 直接帮你获取get set 方法,调用的时候会自己帮我们判断类型)
PropertyDescriptor(String propertyName , Class<?> beanClass);
- propertyName 属性名 ,就是你要进行赋值的属性名
- Class<?> beanClass 就是要修改属性的那个类的模板对象,例如:student.getClass()
//获取一个属性描述器 PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnLabel, c); //利用getWriteMethod()方法获取set方法 //writeMethod 就相当于 该属性的 set方法的模板对象 Method writeMethod = propertyDescriptor.getWriteMethod(); //通过invoke执行方法 writeMethod.invoke(t , object) ;
5、sql注入
- 什么叫做sql注入问题:
- 介绍: 注入攻击 就是使用静态statement进行动态SQL操作的时候!值被误认为SQL语句的一部分!影响了最终的数据操作结果!
- 深度原因:因为statement第一次接触SQL语句就是执行的时刻!只能根据语句的内容去执行!无法筛选和甄别SQL语句原有的结构!
- 简而言之就是,值被当作了sql语句
- 如何避免Sql注入
- 动态SQL语句使用 PreparedStatement 预编译 sql语句 提前记录SQL语句结构!
- 注意:使用预编码statement语句结构中、条件值的部分使用占位符 ?
- 使用语法
- 编写带占位符的SQL语句
- 创建预编译Statement,并传入SQL语句结构
- 占位符赋值(从左开始数?即可!注意从1开始!)
- 调用执行SQL语句的方法!方法与静态一致、只是无需传入SQL语句了!
6、工具类的封装
类的类型介绍
实体类:承接数据!数据库数据的另一种体现!踏踏实实的按照数据库的格式和类型写!
get/set 无参数构造
逻辑类:自身存在一些逻辑功能!可能跟别的类产生关联!
工具类:帮助逻辑类完成或者简化一些操作的类!内部包含很多方法 但是方法之间可能没有关系!
- 注意:工具类没有资格捕捉异常,必须抛出异常,他只是一个工具
工具类编写思想
- 工具类本身没有任何的逻辑可言!就是为了简化其他的逻辑类!提供一些方法!
- 通常一个工具类内部方法方向是统一!
- 工具类通常方法事静态的!或者单例模式!
数据库工具类编写计划
简化注册驱动
- 注册驱动只需要完成一次,因此可以放在静态代码块之中
简化获取连接
在src下创建一个properties文件 ,用于存储信息
driverClass = com.mysql.jdbc.Driver url = jdbc:mysql:///库名 user = root password = 123
然后在工具类之中声明这些信息,在类加载之初,因此使用静态代码块进行赋值
public static String user = null; public static String password = null; public static String url = null; public static String driverClass =null; static{ // 得到一个输入流 必须加 / 代表同级目录 InputStream is = DButils.class.getResourceAsStream("/jdbc.properties"); //Properties可以将属性保存到流中或从流中加载 , 属性列表中的每个键及其对应的值都是一个字符串。 //load(Reader) / store(Writer, String)方法从以下指定的简单的线性导向格式加载和存储基于字符的流的属性。 Properties properties = new Properties(); try { properties.load(is); } catch (IOException e) { e.printStackTrace(); } //通过键返回值 url = properties.getProperty("url"); user= properties.getProperty("user"); password = properties.getProperty("password"); driverClass = properties.getProperty("driverClass"); //注册驱动 try { Class.forName(driverClass); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { Connection connection = DriverManager.getConnection(url, user, password); return connection; }
简化sql语句执行
- 分析需要的方法数量至少3个 ddl dcl tpl 1 , dml 1 (可变参数) , dql 3 返回(list
- ddl
- 修饰符 public static
- 返回值 void
- 参数 string sql
- dml
- 修饰 public static
- 返回值 int
- 参数 sql 可变参数 占位符的值
- dql(单条实体对象)
- 修饰符 public static T xx(sql,CLass clazz ,Object…Params)
- 返回值 T
- 参数 sql、可变占位符、Class clazz
- dql(返回实体类集合)
- dql(返回list
- dql 返回单个Object(聚合函数查询)
7、连接池
- 连接池
- 连接的使用有三个阶段
- T1:创建阶段
- T2: 使用阶段
- T3: 销毁阶段
- 连接池:一个连接的容器!提前创建一些连接加入其中,使用的时候直接获取连接,使用完成则放回去
节省了T1和T3时间,大大的提升了程序的性能。- 手动实现连接
- 创建连接的 static 方法(获取连接的前置操作,比如加载驱动,获得properties中的信息)
- 获取连接的方法 (从容器中拿出来)
- 回收连接的方法 (放回容器中)
- 有一个容器用来存储具体的连接 (用 LinkedList 因为只需要拿第一个,存储到最后一个,因此,LinkedList是最好的选择,因为他有独有的方法用来操作第一个和最后一个的元素 )
7.1标准化的连接池 :实现 DataSource 接口
为什么有标准的DATa Source接口 ?
因为自己写的连接池不通用,不适合用户的习惯,因此通过实现DataSource接口来规范方法
但是当我们实现了接口之后,就会发现,这个接口之中没有实现回收线程的方法,(这是因为。考虑到用户的习惯,通过close来关闭连接,为了让用户用的舒服,因此没有回收连接的方法)
那我们如何解决回收问题呢
- 我们自己写一个实现Connection的实现类,(这个类被称为装饰类)自己实现close方法,让用户调用close方法的时候,能回收到连接池中,让用户不会有突兀 的感觉
但是,自己写的这个类空空的,什么都没有,我们应该怎么办呢?
- 在装饰类之中,先定义一个Connection的属性,写一个有参构造的方法,给他传入一个 真实的 Connection ,将这个connection赋值给属性,然后除了close之外的方法可以交给这个真实的Connection来解决
DataSource介绍:sun公司为了统一连接池实现和统一方法,发布了一套表转化连接池接口!
javax.sql.DataSource接口!所有的第三方连接池都应该实现该接口!
DataSource dataSource = 第三方连接池实现;优点:
- 降低学习曲线,只需要掌握datasource接口方法既可以操作所有连接池
- 保留用户习惯,标准化连接池规范没有设置回收方法,用户只需要像以前一样
- close连接即可!注意:连接池获取的连接,close方法相当于回收!(装饰者模式)
缺点:
- 实现难度加大!需要使用装饰者模式修改Connection的close方法;
实现
- 创建装饰者Connection
- 创建类 实现 Connection接口
- 定义Connection和pool属性
- 重写构造方法,传入连接和pool属性,并复制给全局变量
- 除close方法以外的方法调用真connection执行
- close方法修改成回收躬耕
- 创建连接池工具类
- 创建类 实现 DataSource接口
- 创建Connection容器 LinkedList
- 添加静态代码块
- 在连接池工具类中获取真连接
- 创建装饰者连接传入真连接和pool
- 返回装饰者连接即可
总结:
- 所有的标准连接池都会实现DataSource接口
- 所有来至于连接池的连接 close方法全是回收
- 后面不会使用到我们写的工具类
7.2 c3p0 连接池
导入连接池依赖,即jar包 c3p0-0.9.1.2.jar
编写配置文件:
位置:src文件夹
命名:c3p0-config.xml
<c3p0-config> <!-- 默认配置,如果没有指定则使用这个配置 --> <default-config> <!-- 基本配置 --> <property name="driverClass">com.mysql.jdbc.Driver</property> <property name="jdbcUrl">jdbc:mysql://localhost:3306/库名</property> <property name="user">root</property> <property name="password">123</property> <!--扩展配置--> <!-- 连接超过30秒报错--> <property name="checkoutTimeout">30000</property> <!--30秒检查空闲连接 --> <property name="idleConnectionTestPeriod">30</property> <property name="initialPoolSize">10</property> <!-- 30秒不适用丢弃--> <property name="maxIdleTime">30</property> <property name="maxPoolSize">100</property> <property name="minPoolSize">10</property> <property name="maxStatements">200</property> </default-config> <!-- 命名的配置 --> <named-config name="zhaowf"> <property name="driverClass">com.mysql.jdbc.Driver</property> <property name="jdbcUrl">jdbc:mysql://localhost:3306/day2</property> <property name="user">root</property> <property name="password">111</property> <!-- 如果池中数据连接不够时一次增长多少个 --> <property name="acquireIncrement">5</property> <property name="initialPoolSize">20</property> <property name="minPoolSize">10</property> <property name="maxPoolSize">40</property> <property name="maxStatements">20</property> <property name="maxStatementsPerConnection">5</property> </named-config> </c3p0-config>
创建一个连接池的对象
- DataSource datasource = new ComboPoolDataSource
ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource(); Connection connection = comboPooledDataSource.getConnection(); connection.close();
7.3 druid连接池
导入连接池依赖 ,即jar包 druid-1.1.5.jar
添加配置文件
#连接设置 driverClassName=com.mysql.jdbc.Driver url=jdbc:mysql://localhost:3306/库名 username=root password=root #<!-- 初始化连接 --> initialSize=10 #最大连接数量 maxActive=50 #<!-- 最小空闲连接 --> minIdle=5 #<!-- 超时等待时间以毫秒为单位 60000毫秒/1000等于60秒 --> maxWait=5000
加载配置文件,创建一个连接池
获取连接
- 为了不浪费连接池,可以创建 一个工具类,专门存放连接池,外部可以直接获取连接,释放资源
private static DataSource dataSource = null ; static { //加载配置文件 InputStream resourceAsStream = Connection.class.getResourceAsStream("/database.properties"); Properties properties = new Properties(); try { properties.load(resourceAsStream); } catch (IOException e) { e.printStackTrace(); } //获取连接池 try { dataSource = DruidDataSourceFactory.createDataSource(properties); } catch (Exception e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { //获取连接 return dataSource.getConnection() ; }
7.4 apache DBUtils
(阿帕奇)apache 只要是commons-开头的jaar包,都是阿帕奇旗下的包
导入依赖:commons-dbutils
创建对象:new QueryRunner( ds )
- 传入参数,参数就是一个连接池,但是这样的方法不能进行数据库的事务操作 ,不需要我们关闭连接
- 也可以不传参数,需要每次手动传入连接,后期可以手动的操作数据,需要手动关闭连接 ,可以操作数据库事务
常用的方法
- query DQL
- Query 可以创建的 ResultSetHandle 实现类
- BeanListHandler<实体类>(实体类.class) 将每一条数据封装成Bean装到一个集合返回
- BeanHandler<>()
- ArrayListHandler()
- ArrayHandler
- MapHandler
- MapListHandler
- ScalarHandler ,返回单个Object , 单个聚合函数
- update DML DDL DCL TPL 这些不需要结果集解析器 , 返回受影响的行数
- execute 所有的sql
//创建实例对象 ConnectionUtils 是自己写的一个工具类,用来管理连接池 QueryRunner queryRunner = new QueryRunner(ConnectionUtils.getDataSource()); String sql1 = "create table emp(id int primary key auto_increment , name varchar(20) not null , age int , work varchar(20) , salary double(10,1))" ; //执行sql语句 int update = queryRunner.update(sql1); System.out.println("update = " + update);