• 初识jdbc


    java中的数据存储技术

    • 在Java中,数据库存取技术可分为如下几类:

      • JDBC直接访问数据库

      • JDO (Java Data Object )技术

      • 第三方O/R工具,如Hibernate, Mybatis 等

    • JDBC是java访问数据库的基石,JDO、Hibernate、MyBatis等只是更好的封装了JDBC。jdbc识sun发布的一个java程序和数据库之间通信的规范(接口)

    JDBC体系结构

    JDBC接口(API)包括两个层次:

    • 面向应用的API:Java API,抽象接口,供应用程序开发人员使用(连接数据库,执行SQL语句,获得结果)。

    • 面向数据库的API:Java Driver API,供开发商开发数据库驱动程序用。zhu

    1、JDBC的常规操作

    1). JDBC连接MySQL数据库


    要先导入mysql-jdbc(驱动)jar包(创建一个lib目录,里面存放该jar包,记得要添加为类库。(个大数据库厂商去实现JDBC规范(实现类),这些实现类打成压缩包,就是所谓的jar包。

     mysql驱动的下载MySQL :: Download Connector/J

     (注意:如果是Dynamic Web Project(动态的web项目)话,则是把驱动jar放到WebContent(有的开发工具叫 WebRoot)目录中的WEB-INF目录中的lib目录下即可

    JDBC程序编写步骤:

    1、注册驱动DriverManager

    2、获取数据库连接

    3、创建发送sql语句对象

    4、发送sql语句,并获取返回结果

    5、结果集解析

    6、资源关闭

    1. JDBC常规操作:

    1). JDBC连接MySQL数据库

    关于驱动:

    1. /*
    2. 注册驱动:
    3. 依赖:驱动版本 8+ com.mysql.cj.jdbc.Driver
    4. 驱动版本 5+ com.mysql.jdbc.Driver
    5. */

    加载驱动:加载 JDBC 驱动需调用 Class 类的静态方法 forName(),向其传递要加载的 JDBC 驱动的类名,Class.forName(“com.mysql.jdbc.Driver”);

    注册驱动:DriverManager 类是驱动程序管理器类,负责管理驱动程序

    1. //方案1
    2. // DriverManager.registerDriver(new Driver());
    3. //方案2:注册驱动 固定写法 mysql - mysql Driver || 不灵活,换成oracle 等别的数据库需要再改代码
    4. // new Driver();
    5. //方案3:反射
    6. //字符串的Driver全限定词,可以引导外部的配置文件-》xx.properties -> oracle -> 配置文件修改
    7. Class.forName("com.mysql.cj.jdbc.Driver");
    8. /*
    9. 方案一问题:会注册两次驱动
    10. 1、 DriverManager.registerDriver();方法本身会注册一次
    11. 2、 Driver.static{ DriverManager.registerDriver()} 静态代码块也会注册一次
    12. 解决:只触发静态代码块 Driver
    13. 触发静态代码块:类加载时会触发
    14. 加载【class文件--》java虚拟机和class对象
    15. 连接【验证(检查文件类型)-》准备(静态变量默认值)-》解析(触发静态代码块)
    16. 初始化(静态属性赋真实值)
    17. 触发类加载:
    18. 1、new
    19. 2、调用类的静态方法
    20. 3、调用静态属性
    21. 4、接口 1.8 default默认实现
    22. 5、反射11
    23. 6、子类触发父类 (子类实例化会触发父类)
    24. 7、程序的入口main
    25. */

     (如图如果DriverManager.registerDriver(new Driver());来注册驱动,通常不用显式调用 DriverManager 类的 registerDriver() 方法来注册驱动程序类的实例,则相当于DriverManager.registerDriver调用了两次自己,注册了两次驱动。所以采用方案三来注册驱动合适。

    关于获取数据库连接

    1. //方式1
    2. Connection connection = DriverManager.getConnection("jdbc:mysql:///forget", "root", "1234567");
    3. //方式2
    4. Properties info = new Properties();
    5. info.put("user","root");
    6. info.put("password","1234567");
    7. Connection connection1 = DriverManager.getConnection("jdbc:mysql:///forget", info);
    8. //方式3
    9. DriverManager.getConnection("jdbc:mysql:///forget?user=root&password=1234567");
    10. /*
    11. getConnection(1,2,3)是一个重载方法,下面对其参数形式作解释,上面是示例
    12. 核心属性:
    13. 1、数据库软件所在的主机的ip地址: localhost | 127.0.0.1
    14. 2、数据库软件所在的主机的端口号:3306(默认的端口号是这个)
    15. 3、连接的具体数据库:
    16. 4、连接的账号、密码
    17. 5、可选信息:
    18. 三个参数:
    19. String url:数据库软件所在信息,连接的具体库,以及其他可选信息
    20. 语法:jdbc;数据库管理软件名称【mysql,oracle]://ip地址|主机名:port端口号/数据库名?key=value&key=value 可选信息
    21. 具体: jdbc:mysql://127.0.0.1:3306/forget
    22. jdbc:mysql://localhost:3306/forget
    23. 本机省略写法:如果你的数据库软件安装到本机,可以进行一些省略
    24. jdbc:mysql:///forget (forget是我创建的数据库名字)
    25. String user:
    26. String password:
    27. user,password可以用“属性名=属性值”方式告诉数据库
    28. 也可以调用 DriverManager 类的 getConnection() 方法建立到数据库的连接
    29. 两个参数:
    30. String url:
    31. properties info:储存账号和密码:
    32. properties 类似于Map 只不过key = value 都是字符串形式
    33. key user :账号信息
    34. key password :密码信息
    35. 一个参数::数据库ip,端口号,具体数据库
    36. jdbc:数据库软件名://ip:port/forget?key=value&key=value&key=value
    37. jdbc:数据库软件名://ip:port/forget?key=value&key=value&key=v
    38. */

    URL:JDBC URL 用于标识一个被注册的驱动程序,驱动程序管理器通过这个 URL 选择正确的驱动程序,从而建立到数据库的连接。

    JDBC URL的标准由三部分组成,各部分间用冒号分隔。

    jdbc:子协议:子名称
    协议:JDBC URL中的协议总是jdbc
    子协议:子协议用于标识一个数据库驱动程序

    子名称:一种标识数据库的方法。子名称可以依不同的子协议而变化,用子名称的目的是为了定位数据库,提供足够的信息。包含主机名(对应服务端的ip地址),端口号,数据库名

    1. 几种常用数据库的 JDBC URL
    2. MySQL的连接URL编写方式:
    3. jdbc:mysql://主机名称:mysql服务端口号/数据库名称?参数=值&参数=值
    4. jdbc:mysql://localhost:3306/atguigu
    5. jdbc:mysql://localhost:3306/atguigu?useUnicode=true&characterEncoding=utf8(如果JDBC
    6. 程序与服务器端的字符集不一致,会导致乱码,那么可以通过参数指定服务器端的字符集)
    7. jdbc:mysql://localhost:3306/atguigu?user=root&password=123456
    8. Oracle 9i的连接URL编写方式:
    9. jdbc:oracle:thin:@主机名称:oracle服务端口号:数据库名称
    10. jdbc:oracle:thin:@localhost:1521:atguigu
    11. SQLServer的连接URL编写方式:
    12. jdbc:sqlserver://主机名称:sqlserver服务端口号:DatabaseName=数据库名称
    13. jdbc:sqlserver://localhost:1433:DatabaseName=atguigu

     综上只要注册驱动和获取连接只要使用如下就好

    1. //1、注册驱动
    2. Class.forName("com.mysql.cj.jdbc.Driver");
    3. //2、获取连接
    4. Connection connection = DriverManager.getConnection("jdbc:mysql:///forget", "root", "1234567");

    其它讲法,

    数据库连接的连接方式一:

    1. public void connectiontest1(){
    2. try {
    3. //1.提供java.sql.Driver接口实现类的对象
    4. Driver driver = new com.mysql.cj.jdbc.Driver();
    5. //2.提供url,指明具体操作的数据
    6. String url ="jdbc:mysql://localhost:3306/jdbc";
    7. //3.提供Properties的对象,指明用户名和密码
    8. Properties info = new Properties();
    9. info.setProperty("user","root");
    10. info.setProperty("password","1234567");
    11. //4.调用driver的connect(),获取连接
    12. Connection connection = driver.connect(url,info);
    13. System.out.println(connection);
    14. } catch (SQLException e) {
    15. throw new RuntimeException(e);
    16. }
    17. }
    如果打印结果输出为null,可能是url打错了。打印结果如下形式
    Driver driver = new com.mysql.jdbc.Driver();可根据如下图理解,
    (上述代码中显式出现了第三方数据库的API)

    这里可以改进实例化Driver方式,获取Driver实现类时使用反射,不在代码中体现第三方数据库的API。体现了面向接口编程思想。换成如下

    Class clazz = Class.forName("com.mysql.cj.jdbc.Driver");
    

    方式二:

    使用DriverManager实现数据库的连接。体会获取连接必要的4个基本要素

    1. public void connectiontest2() throws ClassNotFoundException, InstantiationException, IllegalAccessException, SQLException {
    2. //1、实现Driver实现类对象
    3. Class clazz = Class.forName("com.mysql.cj.jdbc.Driver");
    4. Driver driver = (Driver) clazz.newInstance();
    5. //2.数据库连接的3个基本信息:
    6. String url = "jdbc:mysql://localhost:3306/jdbc";
    7. String user = "root";
    8. String password = "1234567";
    9. //3、注册驱动
    10. DriverManager.registerDriver(driver);
    11. //4、获取连接
    12. Connection connection = DriverManager.getConnection(url, user, password);
    13. System.out.println(connection);
    14. }

    上面方法 DriverManager.registerDriver(driver);,前面分析时讲到不太适合。在这个基础上可以删掉,变成

    1. String url = "jdbc:mysql://localhost:3306/jdbc";
    2. String user = "root";
    3. String password = "1234567";
    4. Connection connection = DriverManager.getConnection(url, user, password);
    5. System.out.println(connection);

    (可以省略是因为mysql的Driver实现类中声明:

    1. static {
    2. try {
    3. DriverManager.registerDriver(new Driver());
    4. } catch (SQLException var1) {
    5. throw new RuntimeException("Can't register driver!");
    6. }
    7. }

    因为 Driver 接口的驱动程序类都包含了静态代码块,在这个静态代码块中,会调用 DriverManager.registerDriver() 方法来注册自身的一个实例。所以可以只是加载驱动,不必显式的注册驱动了。因为在DriverManager的源码中已经存在静态代码块,实现了驱动的注册。

    方式三:使用配置文件的方式保存配置信息,在代码中加载配置文件

    但是在mysql中可以省略,但是换成别的数据库可能就不行了,所以

    Class.forName("com.mysql.cj.jdbc.Driver");还是不要省略了,

    创建properties文件存储需要用到的内容(记住=左右不要空格),该文件放在src目录下

    1. public void connectiontest3() throws ClassNotFoundException, InstantiationException, IllegalAccessException, SQLException, IOException {
    2. //1、加载配置文件
    3. InputStream is = ConnectionTest.class.getClassLoader().getResourceAsStream("jdbc.properties");
    4. Properties properties = new Properties();
    5. properties.load(is);//加载这个文件
    6. //2.读取配置信息
    7. String user = properties.getProperty("user");
    8. String password = properties.getProperty("password");
    9. String url = properties.getProperty("url");
    10. String driverClass = properties.getProperty("driverClass");
    11. //3、加载驱动
    12. Class.forName(driverClass);
    13. //4、获取连接
    14. Connection connection = DriverManager.getConnection(url, user, password);
    15. System.out.println(connection);
    16. }

    (①实现了代码和数据的分离,如果需要修改配置信息,直接在配置文件中修改,不需要深入代码 ②如果修改了配置信息,省去重新编译的过程。


    补充:

    (在navicat中导入数据表成功却没有表,遇到这个问题,看到了这篇博客navicat导入sql文件成功但没有表_sql导入成功但是没有数据_czx鑫的博客-CSDN博客



    使用PreparedStatement实现CRUD操作

    操作和访问数据库:数据库连接被用于向数据库服务器发送命令和 SQL 语句,并接受数据库服务器返回的结果。其实一个数据库连接就是一个Socket连接。

    1. 在 java.sql 包中有 3 个接口分别定义了对数据库的调用的不同方式:
    2. Statement:用于执行静态 SQL 语句并返回它所生成结果的对象。
    3. PrepatedStatement:SQL 语句被预编译并存储在此对象中,可以使用此对象多次高效地执行该语句。
    4. CallableStatement:用于执行 SQL 存储过程

    但是使用Statement操作数据表存在弊端:

    1. Scanner scanner = new Scanner(System.in);
    2. System.out.println("请输入账号:");
    3. String account = scanner.nextLine();
    4. System.out.println("请输入密码:");
    5. String password = scanner.nextLine();
    6. Class.forName("com.mysql.cj.jdbc.Driver");
    7. Connection connection=DriverManager.getConnection("jdbc:mysql:///forget?user=root&password=1234567");
    8. //创建发送sql语句的statement对象
    9. //statement 可以发送sql语句到数据库,并且获取返回结果
    10. Statement statement = connection.createStatement();
    11. //发送sql语句
    12. String sql ="select * from user where account = '"+account+"'and password ='"+password+"';";

    statement在后面查询结果集会使用到,如下

    ResultSet resultSet = statement.executeQuery(sql);

    关于statement的使用

    问题一:存在拼串操作,繁琐
    问题二:存在SQL注入问题

    SQL 注入是利用某些系统没有对用户输入的数据进行充分的检查,而在用户输入数据中注入非法的 SQL 语句段或命令。如下

     一开始的sql语句:

    "select * from user where user = '"+user+"'and password ='"+password+"';"

    所以当如下输入时

    user输入1'  or

    密码输入 =1 or '1' = '1 时,会改变sql语句的原意,变成

    SELECT user, password FROM user_table WHERE user='1' OR 1 = ' AND password = ' OR '1' ='1')

    意味着用户名为1或者密码为1或1=1(恒成立)。从而利用系统的 SQL 引擎完成恶意行为的做法。

    对于 Java 而言,要防范 SQL 注入,只要用 PreparedStatement(从Statement扩展而来) 取代 Statement 就可以了。

    PreparedStatement的使用

    可以通过调用 Connection 对象的 preparedStatement(String sql) 方法获取PreparedStatement 对象

    1. //1、注册驱动
    2. Class.forName("com.mysql.cj.jdbc.Driver");
    3. //2、获取连接
    4. Connection connection = DriverManager.getConnection("jdbc:mysql:///forget", "root", "1234567");
    5. //3、编写sql语句结果,动态值的部分使用?代替
    6. String sql = "insert into user(account,password,nickname)values(?,?,?);";
    7. //4、创建preparedStatement,并且传入sql语句结果
    8. PreparedStatement preparedStatement = connection.prepareStatement(sql);

    PreparedStatement 接口是 Statement 的子接口,它表示一条预编译过的 SQL 语句
    PreparedStatement 对象所代表的 SQL 语句中的参数用问号(?)来表示,调用 PreparedStatement 对象的setXxx() 方法来设置这些参数. setXxx() 方法有两个参数,第一个参数是要设置的 SQL 语句中的参数的索引(从 1开始),第二个是设置的 SQL 语句中的参数的值

    1. //5、占位符赋值
    2. preparedStatement.setObject(1, "test");
    3. preparedStatement.setObject(2, "test");
    4. preparedStatement.setObject(3, "lulu");
    相当于:
    insert into user(account,password,nickname)values("test","test","lulu");

    PreparedStatement的增删改

    如下为添加数据操作

    1. public void testInsert() {
    2. Connection connection = null;
    3. PreparedStatement preparedStatement = null;
    4. try {
    5. //1、获取配置信息
    6. InputStream resourceAsStream = ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc.properties");
    7. Properties properties = new Properties();
    8. properties.load(resourceAsStream);
    9. String user = properties.getProperty("user");
    10. String password = properties.getProperty("password");
    11. String url = properties.getProperty("url");
    12. String driverClass = properties.getProperty("driverClass");
    13. //2、加载驱动
    14. Class.forName(driverClass);
    15. //3、获取连接
    16. connection = DriverManager.getConnection(url, user, password);
    17. //4、预编译sql语句,返回PreparedStatement的实例
    18. String sql="insert into customers(name,email,birth) values(?,?,?)";
    19. preparedStatement = connection.prepareStatement(sql);
    20. //5、填充占位符
    21. preparedStatement.setObject(1,"周日");
    22. preparedStatement.setObject(2,"326111111@qq.com");
    23. SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
    24. Date date = simpleDateFormat.parse("1909-02-01");
    25. preparedStatement.setDate(3, new java.sql.Date(date.getTime()));
    26. //util下的date不能转换为sql下的date,所以(java.sql.Date)new (date.getTime())不行
    27. //6、执行操作
    28. preparedStatement.execute();
    29. } catch (Exception e) {
    30. e.printStackTrace();
    31. } finally {
    32. //7、资源的关闭
    33. try {
    34. if(preparedStatement!=null){
    35. preparedStatement.close();
    36. }
    37. } catch (SQLException e) {
    38. e.printStackTrace();
    39. }
    40. try {
    41. if(connection!=null){
    42. connection.close();
    43. }
    44. } catch (SQLException e) {
    45. e.printStackTrace();
    46. }
    47. }
    48. }

    遇到报错

    https://www.cnblogs.com/We612/p/10849556.html,修改字符集即可,我是再navicat上利用图形化界面的操作实现的,但是可以俩姐下相关命令。

    关于字符集,我当时没找到utf8,看到有utfmb3和utfmb4,我设置的是4。

    mysql中没有utf8字符集_mysql之坑–UTF8字符集_帕腓尼基的博客-CSDN博客

    接下来的删、改则通过对jdbc一些步骤封装成工具类后进行使用实现

    1. public class JDBCUtils {
    2. public static Connection getConnection() throws Exception {
    3. //1、获取配置信息
    4. InputStream resourceAsStream = ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc.properties");
    5. Properties properties = new Properties();
    6. properties.load(resourceAsStream);
    7. String user = properties.getProperty("user");
    8. String password = properties.getProperty("password");
    9. String url = properties.getProperty("url");
    10. String driverClass = properties.getProperty("driverClass");
    11. //2、加载驱动
    12. Class.forName(driverClass);
    13. //3、获取连接
    14. Connection connection = DriverManager.getConnection(url, user, password);
    15. return connection;
    16. }
    17. public static void closeResource(Connection con, PreparedStatement ps) {
    18. try {
    19. if (ps != null) {
    20. ps.close();
    21. }
    22. } catch (SQLException e) {
    23. e.printStackTrace();
    24. }
    25. try {
    26. if (con != null) {
    27. con.close();
    28. }
    29. } catch (SQLException e) {
    30. e.printStackTrace();
    31. }
    32. }
    33. }

    如上对加载驱动,创建连接,PreparedStatemen语句、关闭资源等的封装

    1. public void testUpdate() throws Exception {
    2. Connection connection = null;
    3. PreparedStatement ps = null;
    4. try {
    5. //1、通过封装的JDBCUtils工具类获取数据库连接
    6. connection = JDBCUtils.getConnection();
    7. //2、预编译sql语句,返回PreparedStatement的实例
    8. String sql ="update customers set name = ? where id =?";
    9. ps = connection.prepareStatement(sql);
    10. //3、填充占位符
    11. ps.setObject(1,"tom");
    12. ps.setObject(2,18);
    13. //4、执行
    14. ps.execute();
    15. } catch (Exception e) {
    16. e.printStackTrace();
    17. } finally {
    18. //5、关闭资源
    19. JDBCUtils.closeResource(connection,ps);
    20. }
    21. }

    在工具类的基础上我们再改进一下,实现通用的增删改(就是把占位符赋值部分的功能封装

    1. public static void CommonUse(String sql,Object...args){
    2. Connection connection = null;
    3. PreparedStatement preparedStatement = null;
    4. try {
    5. connection = JDBCUtils.getConnection();
    6. preparedStatement = connection.prepareStatement(sql);
    7. for (int i = 0; i < args.length; i++) {
    8. preparedStatement.setObject(i+1,args[i]);
    9. }
    10. preparedStatement.execute();
    11. } catch (Exception e) {
    12. e.printStackTrace();
    13. } finally {
    14. JDBCUtils.closeResource(connection,preparedStatement);
    15. }

    测试

    1. @Test
    2. public void testDelete() throws Exception {
    3. //增
    4. String add="insert into customers(name,email) value(?,?) ";
    5. CommonUse(add,"lucy","488@qq.com");
    6. //删
    7. String dele="delete from customers where id = ? ";
    8. CommonUse(dele,20);
    9. //改
    10. String update ="update customers set email=? where name=?";
    11. CommonUse(update,"2442ew@qq.com","tom");
    12. }

    查询(之前增删改,使用的时execute()方法进行执行操作,因为需要返回结果集的缘故,后面则是其它方法)

    a、查询需要调用PreparedStatement 的 executeQuery() 方法,查询结果是一个ResultSet 对象

    b、ResultSet 对象以逻辑表格的形式封装了执行数据库操作的结果集,ResultSet 接口由数据库厂商提供实现
    c、ResultSet 返回的实际上就是一张数据表。有一个指针指向数据表的第一条记录的前面。
    d、ResultSet 对象维护了一个指向当前数据行的游标,初始的时候,游标在第一行之前,可以通过 ResultSet 对象的 next() 方法移动到下一行。调用 next()方法检测下一行是否有效。若有效,该方法返回 true,且指针下移。相当于Iterator对象的 hasNext() 和 next() 方法的结合体。


    e、当指针指向一行时, 可以通过调用 getXxx(int index) 或 getXxx(int columnName) 获取每一列的值。
    例如: getInt(1), getString("name")
    注意:Java与数据库交互涉及到的相关Java API中的索引都从1开始。
    ResultSet 接口的常用方法:
    boolean next()
    getString()

    ​​​​​​​

    相关查询操作

    (下图表示Java与SQL对应数据类型转换表)

    一个数据表对应一个java类,表中的一条记录对应java类的一个对象,表中的一个字段对应java类的一个属性。我们创建一个Customer类来封装一条记录

    Customer类中,成员变量如下(记得还要生成get和set方法,带参的构造方法)

    1. public class Customer {
    2. private int id;
    3. private String name;
    4. private String email;
    5. private Date brith;

    测试

    1. public void CustomerForQuery() {
    2. Connection connection = null;
    3. PreparedStatement preparedStatement = null;
    4. ResultSet resultSet = null;
    5. try {
    6. connection = JDBCUtils.getConnection();
    7. String sql = "select * from customers";
    8. preparedStatement = connection.prepareStatement(sql);
    9. resultSet = preparedStatement.executeQuery();
    10. while (resultSet.next()) {
    11. Integer id = resultSet.getInt(1);
    12. String name = resultSet.getString(2);
    13. String eamil = resultSet.getString(3);
    14. Date brith = resultSet.getDate(4);
    15. Customer customer = new Customer(id, name, eamil, brith);
    16. System.out.println(customer);
    17. }
    18. } catch (Exception e) {
    19. throw new RuntimeException(e);
    20. } finally {
    21. try {
    22. if (resultSet != null) {
    23. resultSet.close();
    24. }
    25. } catch (Exception e) {
    26. e.printStackTrace();
    27. }
    28. try {
    29. if (preparedStatement != null) {
    30. preparedStatement.close();
    31. }
    32. } catch (Exception e) {
    33. e.printStackTrace();
    34. }
    35. try {
    36. if (connection != null) {
    37. connection.close();
    38. }
    39. } catch (Exception e) {
    40. e.printStackTrace();
    41. }
    42. }
    43. }

    (但是这种是针对某一具体实体类型的数据封装

    ResultSetMetaData

    可用于获取关于 ResultSet 对象中列的类型和属性信息的对象
     

    1. ResultSetMetaData meta = rs.getMetaData();
    2. getColumnName(int column):获取指定列的名称
    3. getColumnLabel(int column):获取指定列的别名
    4. getColumnCount():返回当前 ResultSet 对象中的列数。
    5. getColumnTypeName(int column):检索指定列的数据库特定的类型名称。
    6. getColumnDisplaySize(int column):指示指定列的最大标准宽度,以字符为单位。
    7. isNullable(int column):指示指定列中的值是否可以为 null
    8. isAutoIncrement(int column):指示是否自动为指定列进行编号,这样这些列仍然是只读的。

    如下代码中对它的使用


    出现一个报错

    原来是没有对应字段造成的(在表中字段名为birth,在实体类中为brith,没对应上),还有就是注意查询字段和属性名一一对应(查询的sql语句的编写),(造成如上报错的原因还可能是属性为私有时获取Field用的方法不是getDeclaredField。)

    如下为查询结果:

    Customer{id=1, name='张三', email='wf@126.com', birth=2010-02-02}
     

    a

    总结

     JDBC对数据库进行增、删、改、查

    (补充:

    关于反射

    下图为从内存中看反射

    Class对象是反射的根源。

    在Object类中定义了以下的方法,此方法将被所有子类继承:
    public final Class getClass()

    获取 Class 类的实例 四种方法

    方式1:要求编译期间已知类型 (Class clazz = String.class;)

    方式2:获取对象的运行时类型

    前提:已知某个类的实例,调用该实例的getClass()方法获取Class对象

    Class clazz = "www.atguigu.com".getClass();

    方式3:可以获取编译期间未知的类型

    前提:已知一个类的全类名,且该类在类路径下,可通过Class类的静态方法forName()获取,可能抛出ClassNotFoundException

    方式4:其他方式
    前提:可以用系统类加载对象或自定义加载器对象加载指定路径下的类型

    实例:
    ClassLoader cl = this.getClass().getClassLoader();

    Class clazz4 = cl.loadClass("类的全类名");

    1. /*
    2. 关于java.lang.Class类的理解
    3. 1、类的加载过程
    4. 程序经过javac.exe命令以后,会生成一个或多个字节码文件(.class结尾)
    5. 接着我们使用java.exe命令对某个字节码文件进行解释运行,相当于将某个字节码文件加载到内存中。
    6. 此过程就称为类的加载。加载到内存中的类,我们就称为运行时类,此运行时类,就作为Class的一个实例
    7. 2、换句话说,Class的实例就对应着一个运行时类
    8. 3、加载到内存中的运行时类,会缓存一定时间。在此时间之内,我们可以通过不同的方式来获取此运行时类。
    9. */
    10. //获取Class的实例的方式
    11. @Test
    12. public void test3() throws ClassNotFoundException {
    13. //方式一:调用运行时类的属性: .class
    14. Class clazz1 = Person.class;
    15. //Class clazz1 = Person.class;
    16. System.out.println(clazz1);
    17. //class SE.Reflection.Person
    18. //方式二:调用运行时类的对象
    19. Person p1 = new Person();
    20. Class clazz2 = p1.getClass();
    21. System.out.println(clazz2);
    22. //class SE.Reflection.Person
    23. //方式三:调用Class的静态方法: forName(String classPath)
    24. Class clazz3 = Class.forName("SE.Reflection.Person");//类的全类名,包含包名在内的完整路径
    25. System.out.println(clazz3);
    26. //class SE.Reflection.Person
    27. System.out.println(clazz1 == clazz2);//true
    28. System.out.println(clazz1 == clazz3);//true
    29. //方式四:使用类的加载器:ClassLoader
    30. ClassLoader classLoader = ReflectionTest.class.getClassLoader();
    31. Class clazz4 = classLoader.loadClass("SE.Reflection.Person");
    32. System.out.println(clazz4);
    33. //class SE.Reflection.Person
    34. System.out.println(clazz1 == clazz4);//true
    35. }

    类的加载器(以jdk8为例)

    JVM支持两种类型的类加载器,分别为引导类加载器(Bootstrap ClassLoader)和自定义类加载器(User-Defined ClassLoader)。在程序中我们最常见的类加载器结构主要是如下情况:

    (启动类加载器(引导类加载器,Bootstrap ClassLoader)它用来加载 Java 的核心库( JAVA_HOME/jre/lib/rt.jar 或 sun.boot.class.path 路径下的内容)。用于提供 JVM 自身需要的类。
    • 并不继承自 java.lang.ClassLoader ,没有父加载器。

    扩展类加载器(Extension ClassLoader)由 sun.misc.Launcher$Ex tClassLoader 实现。

    应用程序类加载器(系统类加载器,AppClassLoader)它负责加载环境变量 classpath 或系统属性 java.class.path 指定路径下的类库
    • 应用程序中的类加载器默认是系统类加载器。
    • 它是用户自定义类加载器的默认父加载器
    • 通过 ClassLoader 的 getSystemClassLoader() 方法可以获取到该类加载器

    1. 查看某个类的类加载器对象
    2. 1)获取默认的系统类加载器
    3. ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
    4. System.out.println(systemClassLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2
    5. 2)查看某个类是哪个类加载器加载的
    6. //如果是根加载器加载的类,则会得到null
    7. ClassLoader classloader1 = Class.forName("java.lang.Object").getClassLoader();
    8. System.out.println(classloader1);//null
    9. 3)获取某个类加载器的父加载器
    10. ClassLoader parentClassloader = systemClassLoader.getParent();
    11. System.out.println(parentClassloader);//sun.misc.Launcher$ExtClassLoader@372f7a8d

    关于类加载器的一个主要方法:getResourceAsStream(String str):获取类路径下的指定文件的输入流。

    1. public void test2() throws IOException {
    2. Properties pros = new Properties();
    3. //此时的文件默认在当前的module下
    4. //读取配置文件方式一:
    5. // FileInputStream fis = new FileInputStream("jdbc1.properties");
    6. // FileInputStream fis = new FileInputStream("src//jdbc2.properties");
    7. // pros.load(fis);
    8. //读取配置文件方式二:
    9. //配置文件默认识别为:当前module的src下
    10. // ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
    11. // InputStream is = classLoader.getResourceAsStream("jdbc2.properties");
    12. // pros.load(is);
    13. //获取配置文件中的信息
    14. String user = pros.getProperty("user");
    15. String password = pros.getProperty("password");
    16. System.out.println("user="+user+","+"password="+password);
    17. //user=小小,password=123456
    18. }

    反射的应用

    应用 1 :创建运行时类的对象

    方式1:直接调用Class对象的newInstance()方法

    1. newInstance():调用此方法,创建对应的运行时类的对象
    2. 内部调用了运行时类的空参构造器
    3. 用此方法正常创建运行时类的对象,要求满足:
    4. 1、运行时类必须提供空参构造器
    5. 2、空参的构造器的访问权限得够。通常,设置为public
    6. // Class clazz = Person.class;
    7. // Person obj = (Person) clazz.newInstance();
    8. Class clazz = Person.class; //这样写后面就不用强转
    9. Person obj = clazz.newInstance();
    10. //Person{name='null', age=0}

    方式2:通过获取构造器对象来进行实例化

    1. 1)通过Class类的getDeclaredConstructor(Class … parameterTypes)取得本类的指定形参类型的构造器 2)向构造器的形参中传递一个对象数组进去,里面包含了构造器中所需的各个参数。
    2. 3)通过Constructor实例化对象。
    3. 如果构造器的权限修饰符修饰的范围不可见,也可以调用setAccessible(true)
    4. Class clazz = Person.class;
    5. Constructor[] constructors = clazz.getDeclaredConstructors();
    6. constructors[2].setAccessible(true);
    7. Object obj = constructors[2].newInstance("today");
    8. System.out.println(obj);//Person{name='today', age=0}
    9. //如果构造器有多个,我们通常是根据形参【类型】列表来获取指定的一个构造器的
    10. Constructor constructors1 = clazz.getDeclaredConstructor(String.class);
    11. constructors1.setAccessible(true);
    12. Object obj1 = constructors1.newInstance("today1");
    13. System.out.println(obj1);//Person{name='today1', age=0}

    应用 2 :获取运行时类的完整结构

    可以获取:包、修饰符、类型名、父类(包括泛型父类)、父接口(包括泛型父接口)、成员(属性、构造器、方法)、注解(类上的、方法上的、属性上的)。

    1. //1.实现的全部接口
    2. public Class[] getInterfaces()
    3. //确定此对象所表示的类或接口实现的接口。
    4. //2.所继承的父类
    5. public Class getSuperclass()
    6. //返回表示此 Class 所表示的实体(类、接口、基本类型)的父类的 Class。
    7. //3.全部的构造器
    8. public Constructor[] getConstructors()
    9. //返回此 Class 对象所表示的类的所有public构造方法。
    10. public Constructor[] getDeclaredConstructors()
    11. //返回此 Class 对象表示的类声明的所有构造方法。
    12. //Constructor类中:
    13. //取得修饰符:
    14. public int getModifiers();
    15. //取得方法名称:
    16. public String getName();
    17. //取得参数的类型:
    18. public Class[] getParameterTypes();
    19. //4.全部的方法
    20. public Method[] getDeclaredMethods()
    21. //返回此Class对象所表示的类或接口的全部方法
    22. public Method[] getMethods()
    23. //返回此Class对象所表示的类或接口的public的方法
    24. //Method类中:
    25. public Class getReturnType()
    26. //取得全部的返回值
    27. public Class[] getParameterTypes()
    28. //取得全部的参数
    29. public int getModifiers()
    30. //取得修饰符
    31. public Class[] getExceptionTypes()
    32. //取得异常信息
    33. //5.全部的Field
    34. public Field[] getFields()
    35. //返回此Class对象所表示的类或接口的public的Field。
    36. public Field[] getDeclaredFields()
    37. //返回此Class对象所表示的类或接口的全部Field。
    38. //Field方法中:
    39. public int getModifiers()
    40. //以整数形式返回此Field的修饰符
    41. public Class getType()
    42. //得到Field的属性类型
    43. public String getName()
    44. //返回Field的名称。
    45. //6. Annotation相关
    46. get Annotation(Class annotationClass) getDeclaredAnnotations()
    47. //7.泛型相关
    48. //获取父类泛型类型:
    49. Type getGenericSuperclass()
    50. //泛型类型:
    51. ParameterizedType
    52. //获取实际的泛型类型参数数组:
    53. getActualTypeArguments()
    54. //8.类所在的包
    55. Package getPackage()
    1. public class UseTest{
    2. /*
    3. 获取当前运行时类的属性结构
    4. */
    5. @Test
    6. public void test(){
    7. Class clazz = Person1.class;
    8. //获取属性结构
    9. //getFields():获取当前运行时类及其父类中声明为public访问权限的属性
    10. Field[] fields = clazz.getFields();
    11. for (Field f :
    12. fields) {
    13. System.out.println(f);
    14. //public int SE.Reflection.Person1.id
    15. //public double SE.Reflection.Creature.weight
    16. }
    17. System.out.println("----------------");
    18. //getDeclaredFields():获取当前运行时类中声明的所有属性。(不包含父类中声明的属性
    19. Field[] declaredFields = clazz.getDeclaredFields();
    20. for (Field f :
    21. declaredFields) {
    22. System.out.println(f);
    23. //private java.lang.String SE.Reflection.Person1.name
    24. //int SE.Reflection.Person1.age
    25. //public int SE.Reflection.Person1.id
    26. }
    27. }
    28. //权限修饰符 数据类型 变量名
    29. @Test
    30. public void test2(){
    31. Class clazz = Person1.class;
    32. Field[] declaredFields = clazz.getDeclaredFields();
    33. for (Field f :declaredFields) {
    34. //1、获取权限修饰符
    35. int modifiers = f.getModifiers();
    36. System.out.print(Modifier.toString(modifiers)+"\t");
    37. //2、数据类型
    38. Class type = f.getType();
    39. System.out.print(type.getName()+"\t");
    40. //class java.lang.String type
    41. //java.lang.String type.getName()
    42. //3、变量名
    43. String fName = f.getName();
    44. System.out.println(fName);
    45. System.out.println();
    46. }
    47. }
    48. /*
    49. 获取运行时类的方法结构
    50. */
    51. @Test
    52. public void test3(){
    53. Class clazz= Person1.class;
    54. //getMethods():获取当前运行时类及其父类中声明为public权限的方法
    55. Method[] methods = clazz.getMethods();
    56. for (Method m :
    57. methods) {
    58. System.out.println(m);
    59. //public int SE.Reflection.Person1.compareTo(java.lang.Object)
    60. //public int SE.Reflection.Person1.compareTo(java.lang.String)
    61. //public void SE.Reflection.Person1.info()
    62. //public java.lang.String SE.Reflection.Person1.display(java.lang.String)
    63. //public void SE.Reflection.Creature.eat()
    64. //public final void java.lang.Object.wait() throws java.lang.InterruptedException
    65. //public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
    66. //public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
    67. //public boolean java.lang.Object.equals(java.lang.Object)
    68. //public java.lang.String java.lang.Object.toString()
    69. //public native int java.lang.Object.hashCode()
    70. //public final native java.lang.Class java.lang.Object.getClass()
    71. //public final native void java.lang.Object.notify()
    72. //public final native void java.lang.Object.notifyAll()
    73. }
    74. System.out.println("----------------");
    75. //getDeclaredMethods():获取当前运行时类中声明的所有方法。(不包含父类中声明的方法
    76. Method[] declaredMethods = clazz.getDeclaredMethods();
    77. for (Method m :
    78. declaredMethods) {
    79. System.out.println(m);
    80. //public int SE.Reflection.Person1.compareTo(java.lang.Object)
    81. //public int SE.Reflection.Person1.compareTo(java.lang.String)
    82. //public void SE.Reflection.Person1.info()
    83. //public java.lang.String SE.Reflection.Person1.display(java.lang.String)
    84. //private java.lang.String SE.Reflection.Person1.show(java.lang.String)
    85. }
    86. }
    87. /*
    88. @Xxx
    89. 权限修饰符 返回值类型 方法名(参数类型1,形参名1...) throws XxxException{}
    90. */
    91. @Test
    92. public void test4(){
    93. Class clazz = Person1.class;
    94. //1、获取方法声明的注解
    95. Method[] declaredMethods = clazz.getDeclaredMethods();
    96. for (Method method :
    97. declaredMethods) {
    98. Annotation[] annotations = method.getAnnotations();
    99. for (Annotation a :annotations) {
    100. System.out.print(a);
    101. //@SE.Reflection.MyAnnotation(value=hello)
    102. //@SE.Reflection.MyAnnotation(value=today is Monday)
    103. }
    104. //2、每个方法的权限修饰符
    105. System.out.print(Modifier.toString(method.getModifiers())+"\t");
    106. //3、返回值类型
    107. System.out.print(method.getReturnType().getName()+"\t");
    108. //4、方法名
    109. System.out.print(method.getName());
    110. System.out.print("(");
    111. //5、形参列表
    112. Class[] parameterTypes = method.getParameterTypes();
    113. if(!(parameterTypes==null&¶meterTypes.length==0)){
    114. for (int i=0;i
    115. if(i==parameterTypes.length-1){
    116. System.out.print(parameterTypes[i].getName()+"args_"+i);
    117. break;
    118. }
    119. System.out.print(parameterTypes[i].getName()+"args_"+i+",");
    120. }
    121. }
    122. System.out.print(")");
    123. //6、抛出的异常
    124. Class[] exceptionTypes = method.getExceptionTypes();
    125. if(exceptionTypes.length>0){
    126. for (int i = 0; i < exceptionTypes.length; i++) {
    127. System.out.print("throws ");
    128. if(i==exceptionTypes.length-1){
    129. System.out.print(exceptionTypes[i].getName());
    130. break;
    131. }
    132. System.out.print(exceptionTypes[i].getName()+",");
    133. }
    134. }
    135. System.out.println();
    136. // public volatile int compareTo(java.lang.Objectargs_0)
    137. //public int compareTo(java.lang.Stringargs_0)
    138. //public void info()throws java.lang.ExceptionInInitializerError,throws java.lang.NumberFormatException
    139. //@SE.Reflection.MyAnnotation(value=today is Monday)private java.lang.String show(java.lang.Stringargs_0)
    140. //@SE.Reflection.MyAnnotation(value=hello)public java.lang.String display(java.lang.Stringargs_0)throws java.lang.NullPointerException
    141. }
    142. }
    143. /*
    144. 获取构造器结构
    145. */
    146. @Test
    147. public void test5(){
    148. Class clazz = Person1.class;
    149. //getConstructors():当前运行时类中声明为public的构造器
    150. Constructor[] constructors = clazz.getConstructors();
    151. for (Constructor c :
    152. constructors) {
    153. System.out.println(c);
    154. //public SE.Reflection.Person1()
    155. }
    156. System.out.println();
    157. //getDeclaredConstructors():获取当前运行时类声明的所有的构造器
    158. Constructor[] declaredConstructors = clazz.getDeclaredConstructors();
    159. for (Constructor c:declaredConstructors) {
    160. System.out.println(c);
    161. //SE.Reflection.Person1(java.lang.String,int)
    162. //private SE.Reflection.Person1(java.lang.String)
    163. //public SE.Reflection.Person1()
    164. }
    165. }
    166. /*
    167. 获取运行时类的带泛型的父类
    168. */
    169. @Test
    170. public void test6(){
    171. Class clazz = Person1.class;
    172. Type genericSuperclass = clazz.getGenericSuperclass();
    173. System.out.println(genericSuperclass);
    174. //SE.Reflection.Creature
    175. //获取运行时类的带泛型的父类的泛型
    176. ParameterizedType paramType = (ParameterizedType) genericSuperclass;
    177. //1获取泛型类型
    178. Type[] actualTypeArguments = paramType.getActualTypeArguments();
    179. for (Type t :
    180. actualTypeArguments) {
    181. //System.out.println(t.getTypeName());
    182. // java.lang.String
    183. System.out.println(((Class) t).getName());
    184. //java.lang.String
    185. }
    186. }
    187. /*
    188. 获取运行时类实现的接口
    189. */
    190. @Test
    191. public void test7(){
    192. Class clazz = Person1.class;
    193. Class[] interfaces = clazz.getInterfaces();
    194. for (Class c :
    195. interfaces) {
    196. System.out.println(c);
    197. //interface java.lang.Comparable
    198. //interface SE.Reflection.MyInterface
    199. }
    200. System.out.println();
    201. //获取运行时类的父类实现的接口
    202. Class[] interfaces1 = clazz.getSuperclass().getInterfaces();
    203. for (Class c :
    204. interfaces1) {
    205. System.out.println(c);
    206. //interface java.io.Serializable
    207. }
    208. }
    209. /*
    210. 获取运行时类所在的包
    211. */
    212. @Test
    213. public void test8(){
    214. Class clazz = Person1.class;
    215. Package pack = clazz.getPackage();
    216. System.out.println(pack);
    217. //package SE.Reflection
    218. }
    219. /*
    220. 获取运行时类声明的注解
    221. */
    222. @Test
    223. public void test9(){
    224. Class clazz = Person1.class;
    225. Annotation[] annotations = clazz.getAnnotations();
    226. for (Annotation a :
    227. annotations) {
    228. System.out.println(a);
    229. //@SE.Reflection.MyAnnotation(value=hi)
    230. }
    231. }
    232. }

    调用运行时类中指定的结构:属性、方法、构造器

    1. public class UseTest1 {
    2. /*
    3. 如何操作运行时类中指定的属性---掌握
    4. */
    5. @Test
    6. public void testField() throws NoSuchFieldException, InstantiationException, IllegalAccessException {
    7. Class clazz = Person1.class;
    8. //创建运行时类的对象
    9. Person1 p = (Person1) clazz.newInstance();
    10. //获取指定属性:要求运行时类中属性声明为public
    11. //通常不采用
    12. Field id = clazz.getField("id");
    13. /*
    14. 设置当前属性的值
    15. set():参数1:指明设置哪个对象的属性 参数2:将此属性值设置为多少
    16. */
    17. id.set(p, 11001);
    18. /*
    19. 获取当前属性的值
    20. get():参数1:获取哪个对象的当前属性值
    21. */
    22. int pid = (int) id.get(p);
    23. System.out.println(pid);
    24. System.out.println("------------------");
    25. // 1\getDeclaredField(String fieldname):获取当前运行时类中指定变量名的属性
    26. Field name = clazz.getDeclaredField("name");
    27. //2\保证当前属性是可访问的
    28. name.setAccessible(true);
    29. //3\获取、设置指定对象的此属性值
    30. name.set(p, "forget");
    31. System.out.println(name.get(p));
    32. }
    33. /*
    34. 如何操作运行时类中指定的方法
    35. */
    36. @Test
    37. public void testMethod() throws Exception {
    38. Class clazz = Person1.class;
    39. //创建运行时类的对象
    40. Person1 p = (Person1) clazz.newInstance();
    41. /*
    42. 1、获取指定的某个方法
    43. getDeclaredMethod():参数1:指明获取方法的名称 参数2:指明获取的方法的形参列表
    44. */
    45. Method show = clazz.getDeclaredMethod("show", String.class);
    46. //保证当前方法是可访问的
    47. show.setAccessible(true);
    48. /*
    49. 2、调用方法invoke():参数1:方法的调用者 参数2:给方法形参赋值的实参
    50. invoke()的返回值即为对应类中调用的方法的返回值
    51. */
    52. Object returnValue = show.invoke(p, "china"); //我的国籍是:china
    53. //String nation = show.invoke(p, "china");
    54. System.out.println(returnValue);//china
    55. System.out.println("-------如何调用静态方法------");
    56. // private static void showDesc()
    57. Method showDesc = clazz.getDeclaredMethod("showDesc");
    58. showDesc.setAccessible(true);
    59. //如果调用的运行时类中的方法没有返回值,则此invoke()返回null
    60. Object returnValue1 = showDesc.invoke(null);
    61. //Object returnValue1 = showDesc.invoke(Person1.class);
    62. System.out.println(returnValue1);//null
    63. }
    64. /*
    65. 如何调用运行时类中指定的构造器
    66. */
    67. @Test
    68. public void testConstructor() throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
    69. Class clazz = Person1.class;
    70. //private Person1(String name)
    71. /*
    72. 1\获取指定的构造器
    73. getDeclaredConstructor():参数:指明构造器的参数列表
    74. */
    75. Constructor constructor = clazz.getDeclaredConstructor(String.class);
    76. //2\保证此构造器是可访问的
    77. constructor.setAccessible(true);
    78. //3\调用此构造器创建运行时类的对象
    79. Person1 per = (Person1) constructor.newInstance("Tom");
    80. System.out.println(per);
    81. //Person1{name='Tom', age=0, id=0}
    82. }
    83. }

    补充:

    发现在navicat直接按f6可以进行输入命令行操作,如下

     添加操作时获取自增列主键值

    1. /**
    2. * user插入一条数据并且获取数据库自增长的主键
    3. * 1、创建preparedStatement时,告知带回数据库自增长的主键
    4. * PreparedStatement preparedStatement = connection.prepareStatement(sql,PreparedStatement.RETURN_GENERATED_KEYS);
    5. * 2、获取装主键值的结果集对象,一行一列,获取对应的数据即可
    6. * ResultSet resultSet = preparedStatement.getGeneratedKeys();
    7. * resultSet.next();
    8. * int id = resultSet.getInt(1);
    9. */
    10. @Test
    11. public void resultPrinaryKey() throws SQLException, ClassNotFoundException {
    12. Class.forName("com.mysql.cj.jdbc.Driver");
    13. Connection connection = DriverManager.getConnection("jdbc:mysql:///web", "root", "1234567");
    14. String sql = "insert into tb_student(sname,gender,age) values(?,?,?);";
    15. PreparedStatement preparedStatement = connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
    16. preparedStatement.setObject(1, "wang");
    17. preparedStatement.setObject(2, "女");
    18. preparedStatement.setObject(3, 21);
    19. int update = preparedStatement.executeUpdate();
    20. if (update > 0) {
    21. System.out.println("插入成功!");
    22. ResultSet resultSet = preparedStatement.getGeneratedKeys();
    23. resultSet.next();
    24. int id = resultSet.getInt(1);
    25. System.out.println("id=" + id);
    26. } else {
    27. System.out.println("插入失败!");
    28. }
    29. preparedStatement.close();
    30. connection.close();
    31. }


    批量插入

    批量执行SQL语句

    JDBC的批量处理语句包括下面三个方法:
    addBatch(String):添加需要批量处理的SQL语句或是参数;
    executeBatch():执行批量处理语句;
    clearBatch(): 清空缓存的数据
    通常我们会遇到两种批量执行SQL语句的情况:
    多条SQL语句的批量处理;
    一个SQL语句的批量传参;

    (mysql服务器默认是关闭批处理的,我们需要通过一个参数,让mysql开启批处理的支持。
    * ?rewriteBatchedStatements=true 写在配置文件的url后面)

    1. public void testInsert() throws SQLException, ClassNotFoundException {
    2. /**
    3. * 使用批量插入的方式插入10000条数据
    4. * 1、路径后添加rewriteBatchedStatements=true,允许批量插入
    5. * 2、insert into values 必须写,且语句不能添加;结束
    6. * 3、不是执行语句,是批量添加,addBatch()
    7. * 4、遍历添加 完毕以后,统一批量执行executeBatch()
    8. */
    9. Class.forName("com.mysql.cj.jdbc.Driver");
    10. Connection connection = DriverManager.getConnection("jdbc:mysql:///web?rewriteBatchedStatements=true", "root", "1234567");
    11. String sql = "insert into user(account,password,nickname) values(?,?,?)";
    12. PreparedStatement preparedStatement = connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
    13. long start = System.currentTimeMillis();
    14. for (int i = 0; i < 10000; i++) {
    15. preparedStatement.setObject(1, "w"+i);
    16. preparedStatement.setObject(2, "1s"+i);
    17. preparedStatement.setObject(3, "xs"+i);
    18. preparedStatement.addBatch();//不执行,接到values后面
    19. }
    20. preparedStatement.executeBatch();//执行批量操作
    21. long end = System.currentTimeMillis();
    22. System.out.println("执行10000次数据插入消耗时间:"+(end-start));
    23. preparedStatement.close();
    24. connection.close();
    25. }

    如果做出如下改进,即执行一部分一部分执行,则会提高速度。

    1. for (int i = 0; i < 10000; i++) {
    2. preparedStatement.setObject(1, "w"+i);
    3. preparedStatement.setObject(2, "女"+i);
    4. preparedStatement.setObject(3, i+10);
    5. //1.“攒”sql
    6. preparedStatement.addBatch();
    7. if(i % 500 == 0){
    8. //2.执行
    9. preparedStatement.executeBatch();
    10. //3.清空
    11. preparedStatement.clearBatch();
    12. }

     数据源连接池

    工作原理

    多种开源的数据库连接池

    (特别注意:
    数据源和数据库连接不同,数据源无需创建多个,它是产生数据库连接的工厂,因此整个应用只需要一个数据源即可。
    当数据库访问结束后,程序还是像以前一样关闭数据库连接:conn.close(); 但conn.close()并没有关闭数据库的物理连接,它仅仅把数据库连接释放,归还给了数据库连接池。)

    Druid(德鲁伊)数据库连接池
    Druid是阿里巴巴开源平台上一个数据库连接池实现,它结合了C3P0、DBCP、Proxool等DB池的优点,同时加入了日志监控,可以很好的监控DB池连接和SQL的执行情况,可以说是针对监控而生的DB连接池,可以说是目前最好的连接池之一。

    硬编码方式:如下把参数信息用代码表示(不易修改,灵活性差,不推荐)

    1. public class DruidUsePart {
    2. /**
    3. * 直接使用代码设置连接池连接参数方式
    4. * 1、创建一个druid连接对象
    5. * 2、设置连接池参数【必须|非必须】
    6. * 3、获取连接【通用方法,所有连接池都一样】
    7. * 4、回收连接【通用方法
    8. */
    9. public void testHard() throws SQLException {
    10. //连接池对象
    11. DruidDataSource druidDataSource = new DruidDataSource();
    12. //设置参数
    13. //必须 连接数据库驱动类的全限定符【注册驱动】 | url | user | password
    14. druidDataSource.setUrl("jdbc:mysql:///jdbc");
    15. druidDataSource.setUsername("root");
    16. druidDataSource.setPassword("1234567");
    17. druidDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
    18. //非必须 初始化连接数量,最大的连接数量
    19. druidDataSource.setInitialSize(4);//初始化连接数量
    20. druidDataSource.setMaxActive(19);//最大数量
    21. //获取连接
    22. Connection connection = druidDataSource.getConnection();
    23. //数据库curd
    24. //回收连接
    25. connection.close();//连接池提供的连接,close,就是回收连接
    26. }
    27. }

    软编码方式:如下将配置信息写入配置文件,通过读取配置文件获取参数

    properties文件要自己创建,里面放入上面代码所表示的信息

    getResourceAsStream("druid.properties");注意这里的写法,如果是在src下则直接写文件名

    1. /**
    2. * 通过读取外部配置文件的方法,实例化druid连接池的对象
    3. */
    4. @Test
    5. public void testSoft() throws Exception {
    6. //1、读取外部的配置文件
    7. Properties properties = new Properties();
    8. //src下的文件,可以使用类加载器提供的方法
    9. InputStream resourceAsStream = DruidUsePart.class.getClassLoader().getResourceAsStream("druid.properties");
    10. properties.load(resourceAsStream);
    11. //2、使用连接池的工具类的工厂模式,创建连接池
    12. DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
    13. Connection connection = dataSource.getConnection();
    14. //数据库curd操作在创建连接后的这里实现即可
    15. connection.close();
    16. }

    (补充:配置文件里的相关信息

    把创建连接池封装到一个类中。

    1. public class DruidTest {
    2. private static DataSource dataSource=null;
    3. static {
    4. try {
    5. //1.加载配置文件
    6. Properties properties = new Properties();
    7. InputStream resourceAsStream = DruidTest.class.getClassLoader().getResourceAsStream("jdbc.properties");
    8. properties.load(resourceAsStream);
    9. //2.获取DataSource
    10. dataSource = DruidDataSourceFactory.createDataSource(properties);
    11. } catch (Exception e) {
    12. e.printStackTrace();
    13. }
    14. }
    15. //获取连接
    16. public static Connection getConnection() throws Exception {
    17. return dataSource.getConnection();
    18. }
    19. //关闭资源
    20. public static void close(Connection connection, PreparedStatement preparedStatement){
    21. close(connection,preparedStatement,null);
    22. }
    23. public static void close(Connection connection, PreparedStatement preparedStatement, ResultSet resultSet){
    24. try {
    25. if(resultSet!=null){
    26. resultSet.close();
    27. }
    28. } catch (Exception e) {
    29. e.printStackTrace();
    30. }
    31. try {
    32. if(preparedStatement!=null){
    33. preparedStatement.close();
    34. }
    35. } catch (Exception e) {
    36. e.printStackTrace();
    37. }
    38. try {
    39. if(connection!=null){
    40. connection.close();
    41. }
    42. } catch (Exception e) {
    43. e.printStackTrace();
    44. }
    45. }
    46. }

    Apache-DBUtils实现CRUD操作

    commons-dbutils 是 Apache 组织提供的一个开源 JDBC工具类库,它是对JDBC的简单封装,学习成本极低,并且使用dbutils能极大简化jdbc编码的工作量,同时也不会影响程序的性能。

    a

    • Dbutils三个核心功能

      • QueryRunner中提供对sql语句操作的API.

      • ResultSetHandler接口,用于定义select操作后,怎样封装结果集.

      • DbUtils类是一个工具类,定义了关闭资源与事务处理的方法

    • QueryRunner核心方法

      • QueryRunner(DataSource ds) ;传入参数为连接池

      • update(String sql, Object… params) ,执行insert update delete操作(增删改)

      • query(String sql, ResultSetHandler rsh, Object… params) ,执行 select操作

    • ResultSetHandler结果集处理

    例如测试添加

    1. public void testInsert() {
    2. Connection connection=null;
    3. try {
    4. QueryRunner queryRunner = new QueryRunner();
    5. connection= DruidTest.getConnection();
    6. String sql = "insert into tb_student(sid,sname,gender,age)values(?,?,?,?)";
    7. int update = queryRunner.update(connection, sql, 2, "forget", "女", 19);
    8. System.out.println(update);
    9. } catch (Exception e) {
    10. throw new RuntimeException(e);
    11. }finally {
    12. DruidTest.close(connection,null);
    13. }
    14. }

    删除(如下就是sql语句不同和改变参数即可)

    1. public void testDetele() {
    2. Connection connection=null;
    3. try {
    4. QueryRunner queryRunner = new QueryRunner();
    5. connection= DruidTest.getConnection();
    6. String sql = "delete from tb_student where sid = ?";
    7. int update = queryRunner.update(connection, sql, 2);
    8. System.out.println(update);
    9. } catch (Exception e) {
    10. throw new RuntimeException(e);
    11. }finally {
    12. DruidTest.close(connection,null);
    13. }
    14. }
    15. }

    使用ResultSetHandler结果集处理

    ResultSetHandler接口及实现类

    该接口用于处理 java.sql.ResultSet,将数据按要求转换为另一种形式。
    ResultSetHandler 接口提供了一个单独的方法:Object handle (java.sql.ResultSet .rs)。
    接口的主要实现类:
     

    1. ArrayHandler:把结果集中的第一行数据转成对象数组。
    2. ArrayListHandler:把结果集中的每一行数据都转成一个数组,再存放到List中。
    3. BeanHandler:将结果集中的第一行数据封装到一个对应的JavaBean实例中。
    4. BeanListHandler:将结果集中的每一行数据都封装到一个对应的JavaBean实例中,存放到List里。
    5. ColumnListHandler:将结果集中某一列的数据存放到List中。
    6. KeyedHandler(name):将结果集中的每一行数据都封装到一个Map里,再把这些map再存到一个map
    7. 里,其key为指定的key。
    8. MapHandler:将结果集中的第一行数据封装到一个Map里,key是列名,value就是对应的值。
    9. MapListHandler:将结果集中的每一行数据都封装到一个Map里,然后再存放到List
    10. ScalarHandler:查询单个值对象

    使用ResultSetHandler的实现类:BeanHandler:查询一条记录

    1. public void testSelect1() {
    2. Connection connection=null;
    3. try {
    4. QueryRunner queryRunner = new QueryRunner();
    5. connection= DruidTest.getConnection();
    6. String sql = "select * from tb_student where sid = ?";
    7. BeanHandler handler = new BeanHandler<>(Student.class);
    8. Student query = queryRunner.query(connection, sql, handler, 1);
    9. System.out.println(query);
    10. } catch (Exception e) {
    11. throw new RuntimeException(e);
    12. }finally {
    13. DruidTest.close(connection,null);
    14. }
    15. }

    使用ResultSetHandler的实现类:BeanListHandler:查询多条记录构成的集合

    1. public void testSelect2() {
    2. Connection connection=null;
    3. try {
    4. QueryRunner queryRunner = new QueryRunner();
    5. connection= DruidTest.getConnection();
    6. String sql = "select * from tb_student";
    7. BeanListHandler handler = new BeanListHandler<>(Student.class);
    8. List query = queryRunner.query(connection, sql, handler);
    9. System.out.println(query);
    10. } catch (Exception e) {
    11. throw new RuntimeException(e);
    12. }finally {
    13. DruidTest.close(connection,null);
    14. }
    15. }

    使用ResultSetHandler的实现类:MapHandler:查询第一行数据

    使用ResultSetHandler的实现类:MapListHandler:查询所有数据

    使用ResultSetHandler的实现类:ScalarHandler:查询单个值的对象

    如何查询类似于最大的,最小的,平均的,总和,个数相关的数据,
    * 使用ScalarHandler

    自定义ResultSetHandler的实现类

    1. public void testSelect6() {
    2. Connection connection=null;
    3. try {
    4. QueryRunner queryRunner = new QueryRunner();
    5. connection= DruidTest.getConnection();
    6. String sql = "select * from tb_student";
    7. ResultSetHandler> handler = new ResultSetHandler>() {
    8. @Override
    9. public List handle(ResultSet resultSet) throws SQLException {
    10. List list = new ArrayList<>();
    11. System.out.println("自定义");
    12. while(resultSet.next()){
    13. int sid = resultSet.getInt(1);
    14. String sname = resultSet.getString(2);
    15. String gender = resultSet.getString(3);
    16. int age = resultSet.getInt(4);
    17. list.add(new Student(sid,sname,gender,age));
    18. }
    19. return list;
    20. }
    21. };
    22. List query = queryRunner.query(connection, sql, handler);
    23. query.forEach(System.out::println);
    24. } catch (Exception e) {
    25. throw new RuntimeException(e);
    26. }finally {
    27. DruidTest.close(connection,null);
    28. }
    29. }

    ( ResultSetHandler> handler = new ResultSetHandler>() {这里的泛型决定了下面重写方法的返回值,因为在方法中老是不能返回List集合,好像是因为一开始在泛型这里写的是Student

    输出结果:

    数据库事务

     事务:一组逻辑操作单元,使数据从一种状态变换到另一种状态。
    事务处理(事务操作):保证所有事务都作为一个工作单元来执行,即使出现了故障,都不能改变这种执行方式。当在一个事务中执行多个操作时,要么所有的事务都被提(commit),那么这些修改就永久地保存下来;
    要么数据库管理系统将放弃所作的所有修改,整个事务回滚(rollback)到最初状态。
    为确保数据库中数据的一致性,数据的操纵应当是离散的成组的逻辑单元:当它全部完成时,数据的一致性可以保持,而当这个单元中的一部分操作失败,整个事务应全部视为错误,所有从起始点以后的操作应全部回退到开始状态。

    1. JDBC事务处理
    2. a、数据一旦提交,就不可回滚。
    3. b、数据什么时候意味着提交?
    4. 当一个连接对象被创建时,默认情况下是自动提交事务:每次执行一个 SQL 语句时,如果执行成功,就会
    5. 向数据库自动提交,而不能回滚。
    6. 关闭数据库连接,数据就会自动的提交。如果多个操作,每个操作使用的是自己单独的连接,则无法保证
    7. 事务。即同一个事务的多个操作必须在同一个连接下。
    8. c、JDBC程序中为了让多个 SQL 语句作为一个事务执行:
    9. 调用 Connection 对象的 setAutoCommit(false); 以取消自动提交事务
    10. 在所有的 SQL 语句都成功执行后,调用 commit(); 方法提交事务
    11. 在出现异常时,调用 rollback(); 方法回滚事务

    创建表

    对字段进行设置让其值不能为负,才不会得到如下结果(

    1. public class BankDao {
    2. /**
    3. * 加钱的数据库操作方法(jdbc)
    4. * account 加钱的账号
    5. * money 加钱的金额
    6. *
    7. */
    8. public void add(String account,int money,Connection connection) throws ClassNotFoundException, SQLException {
    9. String sql = "update bank set money = money+? where account =?; ";
    10. PreparedStatement preparedStatement = connection.prepareStatement(sql);
    11. preparedStatement.setObject(1,money);
    12. preparedStatement.setObject(2,account);
    13. preparedStatement.executeUpdate();
    14. System.out.println("加钱成功!");
    15. }
    16. /**
    17. * 减钱的数据库操作方法(jdbc)
    18. * account 减钱的账号
    19. * money 减钱的金额
    20. *
    21. */
    22. public void sub(String account,int money,Connection connection) throws ClassNotFoundException, SQLException {
    23. String sql = "update bank set money = money-? where account = ?; ";
    24. PreparedStatement preparedStatement = connection.prepareStatement(sql);
    25. preparedStatement.setObject(1,money);
    26. preparedStatement.setObject(2,account);
    27. preparedStatement.executeUpdate();
    28. System.out.println("减钱成功!");
    29. }
    30. }
    1. public class BankService {
    2. @Test
    3. public void start() throws SQLException, ClassNotFoundException {
    4. transfer("小熊", "大大", 500);
    5. }
    6. /*
    7. 事务添加是在业务方法中:
    8. 利用try catch代码块,开启事务和提交事务,事务回滚
    9. 将connection传入dao层即可,dao只负责使用,不要close()
    10. */
    11. public void transfer(String addAccount, String subAccount, int money) throws SQLException, ClassNotFoundException {
    12. BankDao bankDao = new BankDao();
    13. Class.forName("com.mysql.cj.jdbc.Driver");
    14. Connection connection = DriverManager.getConnection("jdbc:mysql:///jdbc", "root", "1234567");
    15. try {
    16. //开启事务
    17. connection.setAutoCommit(false);
    18. //执行数据库动作
    19. bankDao.add(addAccount, money, connection);
    20. System.out.println("----------------------");
    21. bankDao.sub(subAccount, money, connection);
    22. //事务提交
    23. connection.commit();
    24. } catch (Exception e) {
    25. // 5.若有异常,则回滚事务
    26. try {
    27. connection.rollback();
    28. } catch (SQLException e1) {
    29. e1.printStackTrace();
    30. }
    31. e.printStackTrace();
    32. } finally {
    33. try {
    34. //6.恢复每次DML操作的自动提交功能
    35. connection.setAutoCommit(true);
    36. } catch (SQLException e) {
    37. e.printStackTrace();
    38. }
    39. //7.关闭连接
    40. connection.close();
    41. }
    42. }
    43. }

    执行后得到

    可以看到数据库中二者的钱数没有变化,可知进行了回滚

    DAO及相关实现类

    DAO:Data Access Object访问数据信息的类和接口,包括了对数据的CRUD(Create、Retrival、Update、
    Delete),而不包含任何业务相关的信息。有时也称作:BaseDAO
    作用:为了实现功能的模块化,更有利于代码的维护和升级。

  • 相关阅读:
    linux查找目录
    java集成minio文件系统
    Java · 方法的使用 · 方法重载 · 方法递归
    FastAPI 学习之路(十七)上传文件
    Windows虚拟机访问网页证书错误问题
    快速弄懂Python3.10的一些新特性与使用场景
    P1664 每日打卡心情好 题解
    【Qt】三种方式实现抽奖小游戏
    Interlay采用Moonbeam路由流动性,为波卡发展更多流动性
    java根据当前日期获取本周和上周的日期区间
  • 原文地址:https://blog.csdn.net/weixin_66196728/article/details/131927095