我竟然把JDBC忘了,一篇适合新手和老手的JDBC完整介绍(详细的不要不要的) 本是古典 何须时尚 2021-07-24 13:40 195阅读 0赞 **宁愿辛苦一阵子,不要辛苦一辈子** ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQwNzQyMjIz_size_16_color_FFFFFF_t_70_pic_center] ### 小弟的目录 ### * JDBC * 什么是JDBC? * JDBC新手(实现最普通功能的JDBC) * * 操作步骤详解: * * 1.加载数据库驱动和注册驱动 * 2.获取连接 * 3.执行sql语句: * 4.关闭连接 * 整体代码: * JDBC强者(对普通JDBC优化) * * Statement中可能遇到的问题 * 用PreparedStatement来解决sql拼接sql注入sql无法解析二进制文件的问题 * 优化后JDBC代码仍然有的缺陷 * JDBC大神(高级) * * * 什么叫数据库连接池技术? * 总结一下使用数据库连接池的特点: * 使用数据库连接池(德鲁伊) # JDBC # # 什么是JDBC? # Java Database Connectivity:java连接数据库 JDBC是代表一组API,一个独立于特定数据库管理系统,通用的SQL数据库存取和操作的公共接口(一组API). SUN公司为了使java代码可以跨数据库,既是指数据库换了,我们JDBC的代码不改变(或少改变)设计了一组公共的接口(标准),规定了所有操作数据库的代码,应使用哪些类型,哪些方法 这些操作数据库的具体代码由数据库厂商来实现,这些实现类,我们称之为数据库驱动,这就意味着,你要连接和操作数据库,就必须加载数据库的驱动程序 那么java代码就可以通过接口+驱动+标准的SQL语句实现java代码和各种数据库的连接和操作. # JDBC新手(实现最普通功能的JDBC) # ## 操作步骤详解: ## ### 1.加载数据库驱动和注册驱动 ### 有两种方式: (1)是直接在源码库中引入,相当于绝对路径,但是是引入型的,将项目打包时,并不会打包驱动的jar文件(占得内存小,但是打包时,会丢失驱动) (2)创建文件,导入型,在项目中创建目录并导入,这是相当于把jar驱动复制在了项目中,打包时会携带(不会丢失驱动,但是占的内存比较大) 注册驱动: Class.forName("com.mysql.jdbc.Driver"); ### 2.获取连接 ### url = "jdbc:mysql://localhost:3306/test"; 主协议 协议 主机名 端口号 数据库名 user = "数据库账号"; password = "数据库密码"; Connection ct = DriverManger.getConnection(url,user,password); ### 3.执行sql语句: ### sql = "insert into dept values(6,'张三',15)"; Statement st = ct.createStatement(); int len = st.executeUpdate(sql); System.out.println(len>0?"添加成功":"添加失败"); 如上是添加数据,凡是添加,删除,修改都是修改操作,都调用executeUpdate()方法,方法的参数是sql语句,返回值是len(int类型),凡是select语句都调用executeQuery()方法,遍历查询,返回值是一个set,要用ResultSet接受 ### 4.关闭连接 ### st.close(); ct.close(); 分开代码,各位客官可能不容易记忆和查看 ### 整体代码: ### import java.sql.Connection; import java.sql.DriverManager; import java.sql.Statement; /** * * 1.注册驱动,加载驱动类到内存中,即内存中有驱动类对象 * 2.获取连接,即登录 * 3.执行sql * (1)编写sql语句 * (2)创建Statement对象 * (3)用Statement来执行sql操作,并接受结果 * 4.关闭连接 * * API: * java.sql.Connection;接口,代表连接 * java.sql.DriverManager, 代表类,驱动管理类 * java.sql.Statement; * *协议,主机地址,端口号,路径(查询字串) * 用java添加一个人到test1库中dept(id,name,money)表中 */ public class dome1 { //先导入驱动数据库驱动 public static void main(String[] args) throws Exception { //1.注册驱动,加载驱动类到内存中,即内存中有驱动类对象 //Class.forName("org.git.mm.mysql.Driver");//旧版驱动注册方式 Class.forName("com.mysql.jdbc.Driver");//新版驱动注册方式 //2.获取连接 String url = "jdbc:mysql://localhost:3306/test1"; String user = "root"; String password = "123456"; //驱动已经加载了,现在用驱动管理类DriverManager Connection connection = DriverManager.getConnection(url,user,password); //3.执行sql语句 //3.1编写sql语句 String sql = "insert into dept values(6,'雷',15)"; //要把sql语句发给服务器端执行,并接受他返回的结果 //3.2创建Statement对象 Statement statement = connection.createStatement(); //3.3用Statement来执行sql操作,并接受结果 //凡是insert,delete,update语句都是更新数据库 //凡是select都是查询query int len =statement.executeUpdate(sql);//返回一个整数,表示多少行收到影响 System.out.println(len>0?"添加成功":"添加失败"); //4.关闭连接 statement.close(); connection.close(); } } # JDBC强者(对普通JDBC优化) # 强者就是能一次干到多个敌人(sql语句),先讲个开胃菜,和优化无关,但是我没放到基础里面,怕让新手感到恐惧(其实我多想了,大家都是很强的) 关键的方法"摘"出来(有些方法,我感觉必须得对数据库事务有些了解,才能食用) * commit()提交事务 * rollback()回滚事务 * setAutoCommit() 参数为false时为手动提交模式,为true时当然是自动提交sql代码模式 import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.SQLException; /** * @author 雷雨 * @date 2020/4/22 16:44 * * 用数据库来处理事务 * mysql中默认是自动提交事务的,执行一句,提交一句 * * 需求 * java中JDBC操作实现: * 1.在数据库中修改名字为小雷的名字修改为胡歌 * 2.在数据库中插入新的数据name为张翰,薪资为21 * * 要求这是一个数据库事务的操作, * 即要么都操作,要么都不操作 * */ public class TestTransAction { public static void main(String[] args) { Connection connection = null; //1.注册驱动 try { Class.forName("com.mysql.jdbc.Driver"); //2.创建连接(因为是数据库事务,所以我们要实现的是在执行多个sql语句时,保证是一个连接) String url = "jdbc:mysql://localhost:3306/test1?useUnicode=true&characterEncoding=utf-8"; String user = "root"; String password ="123456"; connection = DriverManager.getConnection(url, user, password); connection.setAutoCommit(false); updata(connection); insert(connection); connection.commit(); } catch (Exception e) { System.out.println("失败"); try { connection.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } }finally { try { connection.setAutoCommit(true); connection.close(); } catch (SQLException e) { e.printStackTrace(); } } } public static void updata(Connection connection)throws Exception{ String sql = "UPDATE dept set name=? where name=?"; PreparedStatement preparedStatement = connection.prepareStatement(sql); preparedStatement.setObject(1,"胡歌"); preparedStatement.setObject(2,"小白"); int len = preparedStatement.executeUpdate(); preparedStatement.close(); } public static void insert(Connection connection)throws Exception{ String sql = "INSERT into dept values(NULL ,?,?)"; PreparedStatement preparedStatement = connection.prepareStatement(sql); preparedStatement.setObject(1,"张翰"); preparedStatement.setObject(2,21); int len = preparedStatement.executeUpdate(); preparedStatement.close(); } } ## Statement中可能遇到的问题 ## 1.在需要控制台输入多个参数来进行操作是,往往需要sql语句的拼接,而sql语句的拼接是很麻烦的,很容易出错. 2.在控制台输入参数作为sql语句执行(查询中可能遇到导出数据库的情况)的一部分时,可能造成sql语句的**注入**,也是由于引号问题造成的.**造成盗窃信息(关键)** 3.sql拼接不支持blob等二进制类型(比如我们需要给数据库中存储一个照片,一般不把照片存入数据库,因为照片占用的内存很大,影响效率,但是的确存在的情况,这时我们的sql拼接是不支持二进制路劲插入到sql语句的操作,会导致sql语句错误) **这里对前面所说的在执行sql语句中可能出现的问题做一个实例** 下面这个例子我们可以看到在数据库只有三列属性的时候,我们的sql语句的拼接,已经很麻了,很容易发生错误.(虽然这不是很关键) import java.sql.Connection; import java.sql.DriverManager; import java.sql.Statement; import java.util.Scanner; /** * * 描述Statement的第一类问题:sql拼接 *需求:在test数据库中,从java控制台中传入多个参数(id和money),来插入这个人的信息 * Statement中的第一个问题sql语句的拼接,虽然能够正常的运行,但是在sql语句的拼接过程 * 中因为引号很复杂,所以很容易发生错误 */ public class question1 { public static void main(String[] args) throws Exception { Scanner scanner = new Scanner(System.in); System.out.println("请输入要插入的用户的id"); String id = scanner.nextLine(); System.out.println("请输入要插入的用户的姓名"); String name = scanner.nextLine(); System.out.println("请输入要插入的用于的money"); String money = scanner.nextLine(); //Register Database Driver Class.forName("com.mysql.jdbc.Driver"); //Get A Database Connection String url = "jdbc:mysql://localhost:3306/test1?useUnicode=true&characterEncoding=utf8"; String user = "root"; String password = "123456"; Connection connection = DriverManager.getConnection(url, user, password); //Execute sql statement String sql = "insert into dept(id,name,money) values ('"+id+"','"+name+"','"+money+"')"; Statement statement = connection.createStatement(); int len = statement.executeUpdate(sql); System.out.println(len>0?"添加成功":"添加失败"); //Close connection statement.close(); connection.close(); scanner.close(); } } **第二个实例:sql注入** import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.Statement; import java.util.Scanner; /** * @author 雷雨 * @date 2020/4/21 23:07 * * 需求:从键盘输入name,根据这个name查询,属于这个id的其他信息 * *Statement中可能出现的第2个问题就是:sql语句的注入 * */ public class question2 { public static void main(String[] args) throws Exception{ Scanner scanner = new Scanner(System.in); System.out.println("请输入要查询的id"); String name =scanner.nextLine(); Class.forName("com.mysql.jdbc.Driver"); String url = "jdbc:mysql://localhost:3306/test1"; String user = "root"; String password = "123456"; Connection connection = DriverManager.getConnection(url,user,password); String sql = "select * from dept where name ='"+name+"'"; Statement statement = connection.createStatement(); ResultSet resultSet = statement.executeQuery(sql); while (resultSet.next()){ System.out.print(resultSet.getObject(1)+"\t"); System.out.print(resultSet.getObject(2)+"\t"); System.out.print(resultSet.getObject(3)+"\t"); } resultSet.close(); statement.close(); connection.close(); scanner.close(); } } 这个很重要,当我们输入一个正确的人名的时候,想必各位客官都知道能正常的输出,但是如果输入一个:`胡歌' or '1'='1`(数据库中已有name为胡歌的),这时是会报异常呢,还是怎样呢? ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQwNzQyMjIz_size_16_color_FFFFFF_t_70] 各位可能看了截图就明白了:哦,原来说的sql注入是这样啊,**其实我们在控制台的这个输入,就是用了sql语句写在字符串中的特性,将其引号,根据我们的想法拆解,最终得到了一个sql语句运行永远都是true**,所以我们轻松的把数据库中的所有人的信息都盗取出来了. 听我解释的还有点懵的客官,返回代码中认真的分析引号,可以试着把我的输入带进去拆解,看是不是会有新的思维碰撞呢. 对于这些令人烦心的麻烦,我们接下来就要看怎么解决 ## 用PreparedStatement来解决sql拼接sql注入sql无法解析二进制文件的问题 ## **这一部分,我将操作的步骤都总结出来,因为比较简单,我不做多的赘述,实例代码中也有部分注释,会给大家一些提示和思考.** **需要修改的部分** 1.sql语句中用`?`占位符来代替需要传入的字符(数据库中的属性列,或者二进制的路径) 2.用连接对象(connection)创建preparedStatement对象,并传入sql语句 3.perparedStatement对象调用setobject()方法传入属性列或二进制 4.调用执行sql语句的方法,这时不用再传入sql语句了 import java.sql.*; import java.util.Scanner; /** *这里只列举一种,其他的解决方式是类似的,不做赘述 */ public class resolve1 { public static void main(String[] args) throws Exception { Scanner scanner = new Scanner(System.in); System.out.println("请输入要插入的用户的id"); String id = scanner.nextLine(); System.out.println("请输入要插入的用户的姓名"); String name = scanner.nextLine(); System.out.println("请输入要插入的用于的money"); String money = scanner.nextLine(); //Register Database Driver Class.forName("com.mysql.jdbc.Driver"); //Get A Database Connection String url = "jdbc:mysql://localhost:3306/test1?useUnicode=true&characterEncoding=utf8"; String user = "root"; String password = "123456"; Connection connection = DriverManager.getConnection(url, user, password); //Execute sql statement String sql = "insert into dept(id,name,money) values (?,?,?)"; PreparedStatement preparedStatement = connection.prepareStatement(sql); preparedStatement.setObject(1,id); preparedStatement.setObject(2,name); preparedStatement.setObject(3,money); int len = preparedStatement.executeUpdate(); System.out.println(len>0?"添加成功":"添加失败"); //Close connection preparedStatement.close(); connection.close(); scanner.close(); } } ## 优化后JDBC代码仍然有的缺陷 ## 1.注册驱动,获取连接的代码我们重复了很多遍(加大了程序员的工作量和代码复用率很低) 2.每次我们都从mysql中获取连接 * mysql是一个YCP/IP的网络程序,每次获取连接的成本很高(需要三次握手,四次挥手等) * 每个客户端都有单独的线程来维护它的请求,这是会出现的问题: (1)如果很多的客户端都同时去访问服务器,那么mysql的并发量增大,可能会挂,特别是遇到一些程序员,获取连接后不关闭,那么服务器的并发太高会挂 (2)每次高成本获取的链接只使用了一次,太奢侈 我们需要的是:一次获取,多次使用 # JDBC大神(高级) # (1)解决问题一:我们把注册驱动,获取连接封装到一个工具类,不仅可以减少代码量,也为后期代码的重构提供了便利 (2)解决问题二:我们可以使用"数据库连接池"来解决 ### 什么叫数据库连接池技术? ### (1)先创建一个连接池pool,然后在池中先放一些对象,然后程序需要的使用,先去连接池pool中看有没有对象,如果有,就不用创建. (2)我们还可以设置连接池的最大连接数量,如果池中所有连接都在使用的话,那么就让客户端"等待",这样虽然有等待的情况,但是比服务器"挂"了更好一点. (3)在创建连接池时,可以先创建少量的连接,等用户连接数高时,再创建多个连接,直到最大连接数量为止. (4)之前connection.close()相当于真的与服务器断开连接,而在连接池中connection.close的方法是把连接的对象返回给连接池. ### 总结一下使用数据库连接池的特点: ### **1.资源重用** 由于数据库连接是重用的,避免了频繁的创建,释放连接引起的重大的性能开销,在**减少了系统开销**的基础了,还增加了系统运行的平稳性 **2.更快的反映速度** 数据库连接池在初始化中已经创建了少量的连接置于连接池中备用.此时连接的初始化工作均已完成,对于业务请求而言,直接利用现有的连接,而不用自己去创建连接,避免了在创建和释放连接过程中的**时间开销**,从而减少系统反应时间 **3.新的资源分配手段** 对于多应用同享一个数据库的系统而言,可在应用层通过连接池的配置,实现某一应用最大连接数的限制,避免某一应用独占所有的资源. **4.统一的资源管理,避免数据库连接泄露** 在较为完善的数据库连接池中实现,可根据预先的占用超时的设定,强制收回被占用的资源,从而避免了常规数据库中的连接泄露(练级泄露简单来讲就是只连接,而不释放). 有些客官会问了如何使用数据库连接池? ### 使用数据库连接池(德鲁伊) ### ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQwNzQyMjIz_size_16_color_FFFFFF_t_70_pic_center 1] **步骤** 1.引入"德鲁伊"jar包 2.加一个配置文件,配置德鲁伊连接池的参数 数据库连接池的作用:管理连接, 所以我,们从这点出发我们需要配置的参数有: 主机名,端口号,用户名,密码,驱动类名 其他需要配置的参数:初始化连接数,最多连接数… 3.怎么写配置: (1) **在src文件下**(其实并不局限于,但是为了我们后期代码方便操作,否则会抛出java.lang.NullPointerException异常)创建一个`.properties`的文件写入 url =jdbc:mysql://localhost:3306/test1?rewriteBatchedStatements=true&useUnicode=true&characterEncoding=utf-8" username=root password=123456 driverClassName=com.mysql.jdbc.Driver initialSize=10 maxActive=20 maxWait=1000 filters=wall 4.创建连接池 5.创建一个方法:可以在数据库连接池中拿对象 **哎呀,成堆的概念看的人头疼,对于程序员来讲,没有什么是实战更有用的学习方式了** 于是,小二,上代码: import com.alibaba.druid.pool.DruidDataSourceFactory; import javax.sql.DataSource; import java.sql.Connection; import java.sql.SQLException; import java.util.Properties; /** * * * 连接池的使用 */ public class JDBCUtils { private static DataSource ds;//这个的作用在后面,相当加载连接 private static ThreadLocal<Connection> local; static { //静态代码块可以用来初始化我们的静态变量 //(1)把druid.properties文件的数据加载到一个properties的对象中 try { Properties properties = new Properties(); properties.load(JDBCUtils.class.getClassLoader().getResourceAsStream("druid.properties"));//这里要传一个类的加载器的对象 ds = DruidDataSourceFactory.createDataSource(properties); local = new ThreadLocal<>(); } catch (Exception e) { e.printStackTrace(); } } // 这段代码可以使用,但是不能保证在同一线程中(客户端)能共享同一个连接对象 //后面的代码如果用到数据库事务的处理时,就发生问题,不能回滚等 // public static Connection getConnection() throws SQLException { // return ds.getConnection(); // } //这里修改为ThreadLocal来保存同一线程的共享变量: public static Connection getConnection() throws SQLException{ //如果能够在local中能拿到一个连接对象,那么说明当前线程已经拿过了 Connection connection = local.get(); //如果不能获取连接对象,说明之前没拿过 if(connection == null){ connection=ds.getConnection(); local.set(connection); } return connection; } //提供一个关闭连接的方法: public static void free(){ Connection connection =local.get(); if(connection!=null){ local.remove(); //还原数据库为自动提交模式,这样下次再拿到这个连接时,就默认是自动提交 try { connection.setAutoCommit(true); connection.close(); } catch (SQLException e) { e.printStackTrace(); } } } } import java.sql.Connection; import java.sql.PreparedStatement; import java.util.Scanner; /** * 需求: * 从键盘输入一个dept库的信息(id,name,money),存储到dept表中 * */ public class TestJdbcUtils { public static void main(String[] args) throws Exception{ //1.键盘输入 Scanner scanner = new Scanner(System.in); // System.out.println("请输入要插入的id"); // String id = scanner.nextLine(); System.out.println("请输入要插入的name"); String name = scanner.nextLine(); System.out.println("请输入要插入的money"); String money = scanner.nextLine(); //2.获取连接,这里我们使用的是我们自己写的工具类,不使用原来的方法 Connection connection = JDBCUtils.getConnection(); //3.编写sql语句 String sql ="insert into dept(name,money) values(?,?)"; //4.创建PreparedStatement PreparedStatement preparedStatement = connection.prepareStatement(sql); //5.set?的值 // preparedStatement.setObject(1,id); preparedStatement.setObject(1,name); preparedStatement.setObject(2,money); //6.执行更新 int len = preparedStatement.executeUpdate(); System.out.println(len>0?"添加成功":"添加失败"); //7.关闭 preparedStatement.close(); JDBCUtils.free(); scanner.close(); } } 还剩一个封装自己的BasicDAO,但是夜深了,肝不动了,明天还有网课,我太难了,今天的文章就写到这吧,下次专门写一篇博客吧.最后再把今天的毒鸡汤和各位共食:**宁愿辛苦一阵子,不要辛苦一辈子**,一起努力吧. [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQwNzQyMjIz_size_16_color_FFFFFF_t_70_pic_center]: /images/20210724/19490cc2e47c4961a5aafea04ebd23ce.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQwNzQyMjIz_size_16_color_FFFFFF_t_70]: /images/20210724/e9c248c5abc74d87a015916f825e8e1d.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQwNzQyMjIz_size_16_color_FFFFFF_t_70_pic_center 1]: /images/20210724/0ceaba96325549ebbe6792d26d5dec81.png
还没有评论,来说两句吧...