JDBC基础
JDBC是什么
-
jdbc的本质:其实就是官方(sun)公司定义的一套操作所有关系型数据库的规则,即接口。各个数据库厂商去实现这套接口,提供数据库驱动jar包。我们可以使用这套接口(JDBC)编程,真正执行的代码是驱动jar包中的实现类,是一组规范,是sun公司制定的一种关于使用java操作数据库的规范,使我们能通过一次编写,操作不同数据库执行sql,实现这个目的需要我们各个数据库厂商实现关于jdbc接口的类,(各自对应各家的数据库规范)。利用了面向对象语言里面多态的特性。
-
使用jdbc编程的步骤
- 1.导入mysql驱动的jar包,存放到libs,然后 把libs包修改为库文件
- 2.注册驱动(告诉我们jdk,我们使用的是哪个数据库驱动)
- 3.获取数据库连接对象connection
- 4.定义sql语句
- 5.获取执行sql语句的对象statement对象,此对象是用来执行sql语句的
- 6.执行sql,接受返回结果使用ResultSet类
- 7.处理结果,因为数据库有个游标,开始时指向数据库的表头项,最后到数据行结尾行的后一行,同时每次只能取出一列数据,我们需要对此进行处理。
- 释放资源
-
jdbc相关类详述
-
DriverManager:驱动管理对象:功能是注册驱动,告诉我们java程序我们需要使用哪个jar包,我们代码上面写的是Class.forName(“com.mysql.jdbc.Driver”),实际上完成这个操作的是driverManager类里面的静态代码块。
-
获取数据库对象
-
public static Connection getConnection(String url, String user, String password)
-
*url:指定连接的路径
*语法:jdbc:mysql://ip地址(域名):端口号/数据库名
*例子:jdbc:mysql://localhost:3306/db3
*细节:如果连接的是本机mysql服务器且端口号为3306,可以省略为 jdbc:mysql:///数据库连接名
-
-
Connection:数据库连接对象
-
1.功能:
-
1.1获取执行sql的对象
-
*Statement createStatement()
-
*PreparedStatement prepareStatement(String sql)
-
2.管理事务
-
开启事务:setAutoCommit(boolean autoCommit):调用该方法设置参数为false,即开启事务
-
提交事务:commit();
-
*回滚事务:rollback();
-
-
-
Statement:执行sql的对象
- 1.执行sql
- 1.1 boolean execute(String sql):可以执行任意的sql ,不常用(因为他多用于处理相对复杂的sql语句)了解即可
- 1.2 int executeUpdate(String sql)执行DML(insert update delete) DDL(create alert drop)语句
- *返回值:影响的行数,可以通过这个影响的行数判断DML语句是否执行成功
- DML(data manipulation language)数据操纵语言:
- DDL(data definition language)数据库定义语言:
-
ResultSet:结果集对象,封装查询结果的
-
*next() 游标向下移动一行
- getXXX(参数)获取数据
- *XXX代表数据类型 如:int getInt(); String getString();
- $参数:
- 1.int:代表列的编号,从1开始 如:getString(1);
- 2.String代表的是列的名称,如:getDouble(“balance”);
-
*注意:*使用的步骤:
-
1.游标向下移动一行
-
2.判断是否有数据boolean next(),判断当前行是否是最后一行末尾(是否有数据),如果是,则返回false,如果不是则返回true.
-
3.获取数据
-
while(rs.next()){ //6.2获取数据 int id=rs.getInt(1); String name=rs.getString("name"); double balance=rs.getDouble(3); System.out.println(id+"---"+name+"---"+balance); }
-
-
PreparedStatement:执行sql对象
-
1.SQL注入问题:输入密码为 a’ or ‘a’ = 'a
-
2.sql:select * from user where username=‘adsc’ and password=‘a’ or ‘a’ =‘a’;statement处理的是静态的sql语句,这些sql语句在生成的时候已经拼接好了,从而执行,这个时候容易
-
3.解决sql注入问题:使用PreparedStatement对象来解决
预编译的sql:参数使用?作为占位符,在执行sql的时候给sql赋值
-
5.注意:后期都会使用PreparedStatement增删改查的操作
- 1.可以防止sql注入
- 2.效率所有的sql是静态的sql
-
JDBC工具类:JDBCUtils
-
-
每使用一次数据库查询操作都得去注册驱动,获取连接,执行sql,释放资源的过程,比较繁琐,所以我们通过编写一个工具类方便我们使用JDBC进行编程,每次统一申请资源,每次统一释放资源,以此来简化我们书写代码的难度
-
//使用jdbc工具类,实现对数据库操作对象资源的统一管理 /*** * * JDBC工具类 * 工具类所有的方法一般是静态类 * 如果路径中存在空格,则会报如下错误, * https://blog.csdn.net/qq_38454176/article/details/104101972 */ public class JDBCUtils { /** * 3.使用配置文件:第十四行代码开始 * 文件的读取,只需要读取一次即可拿到这些值,使用静态代码块 */ private static String url; private static String user; private static String password; private static String driver; /* 静态代码块里面只能处理异常,不能抛,抛异常需要借用方法 */ static { //读取资源文件,获取值 try { //1.创建Properties集合类。 Properties pro=new Properties(); //2.加载文件 /*java.io.FileNotFoundException: src\jdbc.properties (系统找不到指定的路径。) 原因是我们写的是相对路径,应该写个绝对路径,但是写绝对路径我们修改路径后这里也需要更改*/ //pro.load(new FileReader("src/jdbc.properties")); //2.通过获取src路径下文件的方式---》ClassLoader(类加载器),URL是统一资源标识定位符 /* 类路径出现错误的时候说明这个地方类路径存在转义后空格 ClassLoader classLoader=JDBCUtils.class.getClassLoader(); URL res=classLoader.getResource("jdbc.properties"); String path=res.getPath(); System.out.println(path);*/ ClassLoader classLoader = JDBCUtils.class.getClassLoader(); URL res = classLoader.getResource("jdbc.properties"); String path = res.toURI().getPath();//URL对象转换成字符串前,先调用toURI()方法 System.out.println(path); // pro.load(JDBCUtils.class.getResourceAsStream("jdbc.properties")); pro.load(new FileReader(path)); //3.获取属性赋值 url=pro.getProperty("url"); user=pro.getProperty("user"); password=pro.getProperty("password"); driver=pro.getProperty("driver"); //4.注册驱动 try { Class.forName(driver); } catch (ClassNotFoundException e) { e.printStackTrace(); } } catch (IOException | URISyntaxException e) { e.printStackTrace(); } } /** * 获取连接 * @return 连接对象 */ public static Connection getConnection() throws SQLException { /*1.不传参情况: 如果还按JDBCDemo8那种参数则需要经常改变,把工具类传参,不写死, return DriverManager.getConnection("jdbc:mysql:///db3?useSSL=false","root","root"); */ /*2.传参情况: * 其实这种情况也不好,直接在其他类里面调用工具类和直接写方法没太大区别 * * public static Connection getConnection(String url,String user,String password) throws SQLException{ return DriverManager.getConnection(url,user,password);} */ return DriverManager.getConnection(url,user,password); } /** * 释放资源的方法1 */ public static void close(Statement stmt,Connection conn){ if (stmt!=null) try { stmt.close(); } catch (SQLException e) { e.printStackTrace(); } } if (conn!=null){ try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } /** * 释放资源的方法2 * 重载机制 */ public static void close(ResultSet rs,Statement stmt, Connection conn){ if (rs!=null){//防止空指针异常 try { rs.close(); } catch (SQLException e) { e.printStackTrace(); } } if (stmt!=null){ try { stmt.close(); } catch (SQLException e) { e.printStackTrace(); } } if (conn!=null){ try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } }
Java.util.Properties讲解
- Properties(Java.util.Properties),该类主要用于读取Java的配置文件,不同的编程语言有自己所支持的配置文件,配置文件中很多变量是经常改变的,为了方便用户的配置,能让用户够脱离程序本身去修改相关的变量设置。就像在Java中,其配置文件常为.properties文件,是以键值对的形式进行参数配置的。
- .properties 是以键值对的形式存储的一种配置 文件规范,我们通过键值对的形式保存配置形式,通过properties类进行数据读取
- 实现此类的初始化需要传入一个文件输入流,我们可以采用ClassLoader classLoader=JDBCUtils.class.getClassLoader();从类路径中去获取文件流,也可以使用绝对路径的方式通过FileReader文件输入流的形式,可是这种形式不常见,会出现移植问题。
JDBC操控事务:
-
1.事务:一个包含多个步骤的业务操作。如果这个业务操作被事务管理,则这个步骤要么同时成功,要么同时失败
-
2.操作:
- 1.开启事务
- 2.提交事务
- 3.回滚事务
-
3.使用的是commection管理事务
-
开启事务:setAutoCommit(boolean autoCommit):调用该方法设置参数为false,即开启事务,在执行sql之前开启事务
-
/** * 事务操作 */ public class JDBCDemo10 { public static void main(String[] args) { Connection conn=null; PreparedStatement pstmt1=null; PreparedStatement pstmt2=null; try { //1.获取连接 conn = JDBCUtils.getConnection(); //开启事务: conn.setAutoCommit(false); //2.定义sql //2.1张三-500 String sql1="update account set balance=balance-? where id=?"; //2.2李四+500 String sql2="update account set balance=balance+? where id=?"; //3.获取执行sql对象 pstmt1 = conn.prepareStatement(sql1); pstmt2 = conn.prepareStatement(sql2); //4.设置参数 pstmt1.setDouble(1,500); pstmt1.setInt(2,1); pstmt2.setDouble(1,500); pstmt2.setInt(2,2); //5.执行sql语句 pstmt1.executeUpdate(); //手动制造异常,只有pstmt1成功,2不成功,不符合事务,可以在最开始开启事务 int i=3/0; pstmt2.executeUpdate(); //提交事务 conn.commit(); } catch (Exception e) { //事务回滚 try { if (conn!=null){ conn.rollback(); } } catch (SQLException e1) { e1.printStackTrace(); } e.printStackTrace(); }finally { JDBCUtils.close(pstmt1,conn); JDBCUtils.close(pstmt2,conn); } } }
-
数据库连接池:
-
1)普通的JDBC数据库连接使用 DriverManager 来获取,每次向数据库建立连接的时候都要将 Connection加载到内存中,再验证用户名和密码(得花费0.05s~1s的时间)。需要数据库连接的时候,就向数据库要求一个,执行完成后再断开连接。这样的方式将会消耗大量的资源和时间。数据库的连接资源并没有得到很好的重复利用。若同时有几百人甚至几千人在线,频繁的进行数据库连接操作将占用很多的系统资源,严重的甚至会造成服务器的崩溃。
(2)对于每一次数据库连接,使用完后都得断开。否则,如果程序出现异常而未能关闭,将会导致数据库系统中的内存泄漏,最终将导致重启数据库。(回忆:何为Java的内存泄漏?)
(3)这种开发不能控制被创建的连接对象数,系统资源会被毫无顾及的分配出去,如连接过多,也可能导致内存泄漏,服务器崩溃。 -
为解决传统开发中的数据库连接问题,可以采用数据库连接池技术。
-
1、数据库连接池的基本思想:就是为数据库连接建立一个“缓冲池”。预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从“缓冲池”中取出一个,使用完毕之后再放回去。数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个。数据库连接池在初始化时将创建一定数量的数据库连接放到连接池中,这些数据库连接的数量是由最小数据库连接数来设定的。无论这些数据库连接是否被使用,连接池都将一直保证至少拥有这么多的连接数量。连接池的最大数据库连接数量限定了这个连接池能占有的最大连接数,当应用程序向连接池请求的连接数超过最大连接数量时,这些请求将被加入到等待队列中。
-
2*好处: 1.节约资源 2.用户访问高效 3.实现: 1、标准接口:DataSource javax.sql包下的 1.方法: *获取连接:getConnection() *归还连接:如果连接对象connection是从连接池中获取的,那么调用Connection.close()方法,则不会再关闭连接了, 2.一般我们不去实现它,有数据库厂商来实现 1.C3P0:数据库连接池技术 2.Druid:数据库连接池实现技术,阿里巴巴 4.C3P0:数据库连接池技术 *步骤: 1.导入jar包(两个) C3P0-0.9.5.2.jar mchange-commons-java-0.2.12 jar,不要忘记导入数据库驱动jar包 2.定义配置文件: *名称:c3p0.properties或者c3p0-config.xml *路径:直接将文件放在src目录下面即可 3.创建核心对象 数据库连接池对象 ComboPooledDataSource 4.获取连接:getConnection 5.Druid:数据库连接池实现技术,由阿里巴巴提供的 *步骤: 1.导入jar包 druid-1.0.9.jar和数据库连接jar包 2.定义配置文件: *特点:是propertie文件 *可以叫任意名称,可以放在任意的目录下。(也就是会自动加载) 3.加载配置文件。properties 3.获取数据库连接池对象:通过工厂来获取 DruidDataSourceFactory 4.获取连接:getConnection
-
c3p0数据库连接池demo
c3p0-config.xml <c3p0-config> <!-- 使用默认的配置读取连接池对象 ,也就是不传参的情况下获取DataSource,使用默认的配置--> <default-config> <!-- 连接参数 --> <property name="driverClass">com.mysql.jdbc.Driver</property> <property name="jdbcUrl">jdbc:mysql://localhost:3306/db4</property> <property name="user">root</property> <property name="password">448805512liu.</property> <!-- 连接池参数 --> <!--初始化申请的连接数量--> <property name="initialPoolSize">5</property> <!--最大的连接数量--> <property name="maxPoolSize">10</property> <!--超时时间--> <property name="checkoutTimeout">3000</property> </default-config> <named-config name="otherc3p0"> <!-- 连接参数,传参使用这个 --> <property name="driverClass">com.mysql.jdbc.Driver</property> <property name="jdbcUrl">jdbc:mysql://localhost:3306/db3</property> <property name="user">root</property> <property name="password">root</property> <!-- 连接池参数 --> <property name="initialPoolSize">5</property> <property name="maxPoolSize">8</property> <property name="checkoutTimeout">1000</property> </named-config> </c3p0-config>
/** * c3p0的演示 */ public class C3P0Demo2 { public static void main(String[] args) throws SQLException { //1.创建数据库连接池对象,(这里没有传参,会使用xml里面默认的配置) DataSource ds=new ComboPooledDataSource(); //1.1获取DataSource,使用指定名称配置 //DataSource ds=new ComboPooledDataSource("otherc3p0"); //2.获取连接对象,xml里面设置的最大连接对象为10个,这里如果是11个人同时连接? //会报错,此时我们应该怎么提前设置归还到连接池里的操作 for (int i = 1; i <=11; i++) { Connection conn = ds.getConnection(); //3.打印 System.out.println(i+":"+conn); if (i==5){ conn.close();//归还到连接池中 } } } //测试的时候:可以在这里用public static void testNameConfig() throws SQLException,直接载主方法调用方法名 public void testNameConfig() throws SQLException { //1.1获取DataSource.使用指定名称配额 DataSource ds=new ComboPooledDataSource("otherc3p0"); //2.获取连接 for (int i = 1; i <=10; i++) { Connection conn = ds.getConnection(); //3.打印 System.out.println(i+":"+conn); } } }
-
Druid数据库连接池技术
-
druid.properties配置文件 driverClassName=com.mysql.jdbc.Driver url=jdbc:mysql://127.0.0.1/db3 username=root password=448805512liu. initialSize=5 maxActive=10 maxWait=3000
-
/** * Druid演示 */ public class DruidDemo { public static void main(String[] args) throws Exception { //1.导入jar包 //2.定义配置文件 //3.加载配置文件 Properties pro=new Properties(); InputStream is = DruidDemo.class.getClassLoader().getResourceAsStream("druid.properties"); pro.load(is); //4.获取连接池对象 DataSource ds = DruidDataSourceFactory.createDataSource(pro); //5.获取连接 Connection conn=ds.getConnection(); System.out.println(conn); } }
-
-
使用工具类使用数据库连接池技术
-
package cn.cn.intcase.datasource.utils; /*** * Druid连接池工具类 */ public class JDBCUtils { //1.定义成员变量DataSource private static DataSource ds; static { try { //1.加载配置文件 Properties pro=new Properties(); pro.load(JDBCUtils.class.getClassLoader().getResourceAsStream("druid.properties")); //2.获取DataSource ds= DruidDataSourceFactory.createDataSource(pro); } catch (IOException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } } /** * 获取连接 */ public static Connection getConnection() throws SQLException { return ds.getConnection(); } /** * 释放资源 */ public static void close(Statement stmt,Connection conn){ /* if (stmt!=null){ try { stmt.close(); } catch (SQLException e) { e.printStackTrace(); } } if (conn!=null){ try { conn.close();//归还连接 } catch (SQLException e) { e.printStackTrace(); } }*/ close(stmt,conn); } public static void close2(ResultSet rs,Statement stmt, Connection conn){ if (rs!=null){ try { rs.close(); } catch (SQLException e) { e.printStackTrace(); } } if (stmt!=null){ try { stmt.close(); } catch (SQLException e) { e.printStackTrace(); } } if (conn!=null){ try { conn.close();//归还连接 } catch (SQLException e) { e.printStackTrace(); } } } /* 获取连接池对象 * */ public static DataSource getDataSource(){ return ds; } }
-
package cn.intcase.datasource.druid; /** * 使用新的工具类:此时我们就可以快乐的使用数据库连接对象了 */ public class DruidDemo2 { public static void main(String[] args) { /** * 完成添加操作给account表添加记录 */ Connection conn=null; PreparedStatement pstmt=null; try { //1.获取连接 conn = JDBCUtils.getConnection(); //2.定义sql String sql="insert into account values(null,?,?)"; //3.获取psmt对象 pstmt=conn.prepareStatement(sql); //4.给?赋值 pstmt.setString(1,"王五"); pstmt.setDouble(2,3000); //5.执行sql int count=pstmt.executeUpdate(); System.out.println(count); } catch (SQLException e) { e.printStackTrace(); }finally{ //6.释放资源 JDBCUtils.close(pstmt,conn); } } }
-
JDBCTemplate对象简化JDBC的开发
-
* Spring框架对JDBC的简单封装。提供了一个JDBCTemplate对象简化JDBC的开发 * 步骤: 1. 导入jar包 2. 创建JdbcTemplate对象。依赖于数据源DataSource * JdbcTemplate template = new JdbcTemplate(ds); 3. 调用JdbcTemplate的方法来完成CRUD的操作 * update():执行DML语句。增、删、改语句 * queryForMap():查询结果将结果集封装为map集合,将列名作为key,将值作为value 将这条记录封装为一个map集合 * 注意:这个方法查询的结果集长度只能是1 * queryForList():查询结果将结果集封装为list集合 * 注意:将每一条记录封装为一个Map集合,再将Map集合装载到List集合中 * query():查询结果,将结果封装为JavaBean对象 * query的参数:RowMapper * 一般我们使用BeanPropertyRowMapper实现类。可以完成数据到JavaBean的自动封装 * new BeanPropertyRowMapper<类型>(类型.class) * queryForObject:查询结果,将结果封装为对象 * 一般用于聚合函数的查询
-
public class JdbcTemplateDemo2 { //Junit单元测试,可以让方法独立执行 //1. 获取JDBCTemplate对象 private JdbcTemplate template = new JdbcTemplate(JDBCUtils.getDataSource()); /** * 1. 修改1号数据的 salary 为 10000 */ @Test public void test1(){ //2. 定义sql String sql = "update emp set salary = 10000 where id = 1001"; //3. 执行sql int count = template.update(sql); System.out.println(count); } /** * 2. 添加一条记录 */ @Test public void test2(){ String sql = "insert into emp(id,ename,dept_id) values(?,?,?)"; int count = template.update(sql, 1015, "郭靖", 10); System.out.println(count); } /** * 3.删除刚才添加的记录 */ @Test public void test3(){ String sql = "delete from emp where id = ?"; int count = template.update(sql, 1015); System.out.println(count); } /** * 4.查询id为1001的记录,将其封装为Map集合 * 注意:这个方法查询的结果集长度只能是1 */ @Test public void test4(){ String sql = "select * from emp where id = ? or id = ?"; Map<String, Object> map = template.queryForMap(sql, 1001,1002); System.out.println(map); //{id=1001, ename=孙悟空, job_id=4, mgr=1004, joindate=2000-12-17, salary=10000.00, bonus=null, dept_id=20} } /** * 5. 查询所有记录,将其封装为List */ @Test public void test5(){ String sql = "select * from emp"; List<Map<String, Object>> list = template.queryForList(sql); for (Map<String, Object> stringObjectMap : list) { System.out.println(stringObjectMap); } } /** * 6. 查询所有记录,将其封装为Emp对象的List集合 */ @Test public void test6(){ String sql = "select * from emp"; List<Emp> list = template.query(sql, new RowMapper<Emp>() { @Override public Emp mapRow(ResultSet rs, int i) throws SQLException { Emp emp = new Emp(); int id = rs.getInt("id"); String ename = rs.getString("ename"); int job_id = rs.getInt("job_id"); int mgr = rs.getInt("mgr"); Date joindate = rs.getDate("joindate"); double salary = rs.getDouble("salary"); double bonus = rs.getDouble("bonus"); int dept_id = rs.getInt("dept_id"); emp.setId(id); emp.setEname(ename); emp.setJob_id(job_id); emp.setMgr(mgr); emp.setJoindate(joindate); emp.setSalary(salary); emp.setBonus(bonus); emp.setDept_id(dept_id); return emp; } }); for (Emp emp : list) { System.out.println(emp); } } /** * 6. 查询所有记录,将其封装为Emp对象的List集合 */ @Test public void test6_2(){ String sql = "select * from emp"; List<Emp> list = template.query(sql, new BeanPropertyRowMapper<Emp>(Emp.class)); for (Emp emp : list) { System.out.println(emp); } } /** * 7. 查询总记录数 */ @Test public void test7(){ String sql = "select count(id) from emp"; Long total = template.queryForObject(sql, Long.class); System.out.println(total); } }