初识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数据库

关于驱动:

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

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

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


        //方案1
       // DriverManager.registerDriver(new Driver());

        //方案2:注册驱动 固定写法 mysql - mysql Driver || 不灵活,换成oracle 等别的数据库需要再改代码
       // new Driver();

        //方案3:反射
        //字符串的Driver全限定词,可以引导外部的配置文件-》xx.properties -> oracle -> 配置文件修改
        Class.forName("com.mysql.cj.jdbc.Driver");


/*
        方案一问题:会注册两次驱动
        1、 DriverManager.registerDriver();方法本身会注册一次
        2、 Driver.static{ DriverManager.registerDriver()} 静态代码块也会注册一次

        解决:只触发静态代码块 Driver
        触发静态代码块:类加载时会触发
                      加载【class文件--》java虚拟机和class对象
                      连接【验证(检查文件类型)-》准备(静态变量默认值)-》解析(触发静态代码块)
                      初始化(静态属性赋真实值)
         触发类加载:
                 1、new
                 2、调用类的静态方法
                 3、调用静态属性
                 4、接口  1.8 default默认实现
                 5、反射11
                 6、子类触发父类 (子类实例化会触发父类)
                 7、程序的入口main
*/

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

关于获取数据库连接

        //方式1
        Connection connection = DriverManager.getConnection("jdbc:mysql:///forget", "root", "1234567");
        //方式2
        Properties info = new Properties();
        info.put("user","root");
        info.put("password","1234567");
        Connection connection1 = DriverManager.getConnection("jdbc:mysql:///forget", info);
        //方式3
        DriverManager.getConnection("jdbc:mysql:///forget?user=root&password=1234567");


/*
        getConnection(1,2,3)是一个重载方法,下面对其参数形式作解释,上面是示例

        核心属性:
          1、数据库软件所在的主机的ip地址: localhost | 127.0.0.1
          2、数据库软件所在的主机的端口号:3306(默认的端口号是这个)
          3、连接的具体数据库:
          4、连接的账号、密码
          5、可选信息:


          三个参数:
          String url:数据库软件所在信息,连接的具体库,以及其他可选信息
                 语法:jdbc;数据库管理软件名称【mysql,oracle]://ip地址|主机名:port端口号/数据库名?key=value&key=value 可选信息
                 具体: jdbc:mysql://127.0.0.1:3306/forget
                       jdbc:mysql://localhost:3306/forget
                本机省略写法:如果你的数据库软件安装到本机,可以进行一些省略
                       jdbc:mysql:///forget  (forget是我创建的数据库名字)

          String user:
          String password:
        user,password可以用“属性名=属性值”方式告诉数据库
        也可以调用 DriverManager 类的 getConnection() 方法建立到数据库的连接

          两个参数:
          String url:
          properties info:储存账号和密码:
                     properties 类似于Map 只不过key = value 都是字符串形式
                     key user :账号信息
                     key password :密码信息

          一个参数::数据库ip,端口号,具体数据库
                   jdbc:数据库软件名://ip:port/forget?key=value&key=value&key=value
                     jdbc:数据库软件名://ip:port/forget?key=value&key=value&key=v
         */
       

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

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

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

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

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

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

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

其它讲法,

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

public void connectiontest1(){
        try {
            //1.提供java.sql.Driver接口实现类的对象
            Driver driver = new com.mysql.cj.jdbc.Driver();
            //2.提供url,指明具体操作的数据
            String url ="jdbc:mysql://localhost:3306/jdbc";
            //3.提供Properties的对象,指明用户名和密码
            Properties info = new Properties();
            info.setProperty("user","root");
            info.setProperty("password","1234567");
            //4.调用driver的connect(),获取连接
            Connection connection = driver.connect(url,info);
            System.out.println(connection);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }

    }
如果打印结果输出为null,可能是url打错了。打印结果如下形式
Driver driver = new com.mysql.jdbc.Driver();可根据如下图理解,
(上述代码中显式出现了第三方数据库的API)

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

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

方式二:

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

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

    }

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

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

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

static {
            try {
                DriverManager.registerDriver(new Driver());
            } catch (SQLException var1) {
                throw new RuntimeException("Can't register driver!");
            }
        }

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

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

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

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

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

public void connectiontest3() throws ClassNotFoundException, InstantiationException, IllegalAccessException, SQLException, IOException {
        //1、加载配置文件
        InputStream is = ConnectionTest.class.getClassLoader().getResourceAsStream("jdbc.properties");
        Properties properties = new Properties();
        properties.load(is);//加载这个文件
        //2.读取配置信息
        String user = properties.getProperty("user");
        String password = properties.getProperty("password");
        String url = properties.getProperty("url");
        String driverClass = properties.getProperty("driverClass");

        //3、加载驱动
         Class.forName(driverClass);
         //4、获取连接
        Connection connection = DriverManager.getConnection(url, user, password);
        System.out.println(connection);
    }

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


补充:

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



使用PreparedStatement实现CRUD操作

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

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

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

    Scanner scanner = new Scanner(System.in);
        System.out.println("请输入账号:");
        String account = scanner.nextLine();
        System.out.println("请输入密码:");
        String password = scanner.nextLine();  
        Class.forName("com.mysql.cj.jdbc.Driver");  
        Connection connection=DriverManager.getConnection("jdbc:mysql:///forget?user=root&password=1234567");

        //创建发送sql语句的statement对象
        //statement 可以发送sql语句到数据库,并且获取返回结果
        Statement statement = connection.createStatement();

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

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

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

PreparedStatement的增删改

如下为添加数据操作

 public void testInsert() {
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        try {
            //1、获取配置信息
            InputStream resourceAsStream = ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc.properties");
            Properties properties = new Properties();
            properties.load(resourceAsStream);
            String user = properties.getProperty("user");
            String password = properties.getProperty("password");
            String url = properties.getProperty("url");
            String driverClass = properties.getProperty("driverClass");

            //2、加载驱动
            Class.forName(driverClass);
            //3、获取连接
            connection = DriverManager.getConnection(url, user, password);
            //4、预编译sql语句,返回PreparedStatement的实例
            String sql="insert into customers(name,email,birth) values(?,?,?)";
            preparedStatement = connection.prepareStatement(sql);
            //5、填充占位符
            preparedStatement.setObject(1,"周日");
            preparedStatement.setObject(2,"326111111@qq.com");
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
            Date date = simpleDateFormat.parse("1909-02-01");
            preparedStatement.setDate(3,  new java.sql.Date(date.getTime()));
            //util下的date不能转换为sql下的date,所以(java.sql.Date)new (date.getTime())不行
            //6、执行操作
            preparedStatement.execute();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //7、资源的关闭
            try {
                if(preparedStatement!=null){
                    preparedStatement.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
            try {
                if(connection!=null){
                    connection.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

遇到报错

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

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

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

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

public class JDBCUtils {
        public static Connection getConnection() throws Exception {

                //1、获取配置信息
                InputStream resourceAsStream = ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc.properties");
                Properties properties = new Properties();
                properties.load(resourceAsStream);
                String user = properties.getProperty("user");
                String password = properties.getProperty("password");
                String url = properties.getProperty("url");
                String driverClass = properties.getProperty("driverClass");

                //2、加载驱动
                Class.forName(driverClass);
                //3、获取连接
                Connection connection = DriverManager.getConnection(url, user, password);
                return connection;
        }

        public static void closeResource(Connection con, PreparedStatement ps) {
                try {
                        if (ps != null) {
                                ps.close();
                        }
                } catch (SQLException e) {
                        e.printStackTrace();
                }
                try {
                        if (con != null) {
                                con.close();
                        }
                } catch (SQLException e) {
                        e.printStackTrace();
                }
        }
}

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

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

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

 public static void CommonUse(String sql,Object...args){
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        try {
            connection = JDBCUtils.getConnection();
            preparedStatement = connection.prepareStatement(sql);
            for (int i = 0; i < args.length; i++) {
                preparedStatement.setObject(i+1,args[i]);
            }
            preparedStatement.execute();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResource(connection,preparedStatement);
        }
   

测试

 @Test
    public void testDelete() throws Exception {
        //增
        String add="insert into customers(name,email) value(?,?) ";
        CommonUse(add,"lucy","488@qq.com");
        //删
        String dele="delete from customers where id = ? ";
        CommonUse(dele,20);
        //改
        String update ="update customers set email=? where name=?";
        CommonUse(update,"2442ew@qq.com","tom");
    }

查询(之前增删改,使用的时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方法,带参的构造方法)

public class Customer {
    private int id;
    private String name;
    private String email;
    private Date brith;

测试

public void CustomerForQuery() {
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;
        try {
            connection = JDBCUtils.getConnection();
            String sql = "select * from customers";
            preparedStatement = connection.prepareStatement(sql);
            resultSet = preparedStatement.executeQuery();
            while (resultSet.next()) {
                Integer id = resultSet.getInt(1);
                String name = resultSet.getString(2);
                String eamil = resultSet.getString(3);
                Date brith = resultSet.getDate(4);
                Customer customer = new Customer(id, name, eamil, brith);
                System.out.println(customer);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            try {
                if (resultSet != null) {
                    resultSet.close();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            try {
                if (preparedStatement != null) {
                    preparedStatement.close();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            try {
                if (connection != null) {
                    connection.close();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

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

ResultSetMetaData

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

ResultSetMetaData meta = rs.getMetaData();
getColumnName(int column):获取指定列的名称
getColumnLabel(int column):获取指定列的别名
getColumnCount():返回当前 ResultSet 对象中的列数。
getColumnTypeName(int column):检索指定列的数据库特定的类型名称。
getColumnDisplaySize(int column):指示指定列的最大标准宽度,以字符为单位。
isNullable(int column):指示指定列中的值是否可以为 null。
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("类的全类名");

  /*
    关于java.lang.Class类的理解
    1、类的加载过程
    程序经过javac.exe命令以后,会生成一个或多个字节码文件(.class结尾)
    接着我们使用java.exe命令对某个字节码文件进行解释运行,相当于将某个字节码文件加载到内存中。
    此过程就称为类的加载。加载到内存中的类,我们就称为运行时类,此运行时类,就作为Class的一个实例

    2、换句话说,Class的实例就对应着一个运行时类
    3、加载到内存中的运行时类,会缓存一定时间。在此时间之内,我们可以通过不同的方式来获取此运行时类。
     */
    //获取Class的实例的方式
    @Test
    public void test3() throws ClassNotFoundException {
        //方式一:调用运行时类的属性: .class
        Class clazz1 = Person.class;
        //Class<Person> clazz1 = Person.class;
        System.out.println(clazz1);
        //class SE.Reflection.Person

        //方式二:调用运行时类的对象
        Person p1 = new Person();
        Class clazz2 = p1.getClass();
        System.out.println(clazz2);
        //class SE.Reflection.Person

        //方式三:调用Class的静态方法: forName(String classPath)
        Class clazz3 = Class.forName("SE.Reflection.Person");//类的全类名,包含包名在内的完整路径
        System.out.println(clazz3);
        //class SE.Reflection.Person

        System.out.println(clazz1 == clazz2);//true
        System.out.println(clazz1 == clazz3);//true

        //方式四:使用类的加载器:ClassLoader
        ClassLoader classLoader = ReflectionTest.class.getClassLoader();
        Class clazz4 = classLoader.loadClass("SE.Reflection.Person");
        System.out.println(clazz4);
        //class SE.Reflection.Person
        System.out.println(clazz1 == clazz4);//true
    }

类的加载器(以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)获取默认的系统类加载器
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
        System.out.println(systemClassLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2
       (2)查看某个类是哪个类加载器加载的
        //如果是根加载器加载的类,则会得到null
        ClassLoader classloader1 = Class.forName("java.lang.Object").getClassLoader();
        System.out.println(classloader1);//null
       (3)获取某个类加载器的父加载器
        ClassLoader parentClassloader = systemClassLoader.getParent();
        System.out.println(parentClassloader);//sun.misc.Launcher$ExtClassLoader@372f7a8d

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

public void test2() throws IOException {
        Properties pros = new Properties();
        //此时的文件默认在当前的module下
        //读取配置文件方式一:
//        FileInputStream fis = new FileInputStream("jdbc1.properties");
       // FileInputStream fis = new FileInputStream("src//jdbc2.properties");
      // pros.load(fis);

        //读取配置文件方式二:
        //配置文件默认识别为:当前module的src下
//        ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
//        InputStream is = classLoader.getResourceAsStream("jdbc2.properties");
//        pros.load(is);

        //获取配置文件中的信息
        String user = pros.getProperty("user");
        String password = pros.getProperty("password");
        System.out.println("user="+user+","+"password="+password);
        //user=小小,password=123456
    }

反射的应用

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

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

newInstance():调用此方法,创建对应的运行时类的对象
        内部调用了运行时类的空参构造器
        用此方法正常创建运行时类的对象,要求满足:
        1、运行时类必须提供空参构造器
        2、空参的构造器的访问权限得够。通常,设置为public

//        Class clazz = Person.class;
//        Person obj = (Person) clazz.newInstance();

       Class<Person> clazz = Person.class; //这样写后面就不用强转
        Person obj = clazz.newInstance();
      //Person{name='null', age=0} 

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

1)通过Class类的getDeclaredConstructor(Class … parameterTypes)取得本类的指定形参类型的构造器 2)向构造器的形参中传递一个对象数组进去,里面包含了构造器中所需的各个参数。 
3)通过Constructor实例化对象。
如果构造器的权限修饰符修饰的范围不可见,也可以调用setAccessible(true)

        Class<Person> clazz = Person.class;
        Constructor<?>[] constructors = clazz.getDeclaredConstructors();
        constructors[2].setAccessible(true);
        Object obj = constructors[2].newInstance("today");
        System.out.println(obj);//Person{name='today', age=0}

        //如果构造器有多个,我们通常是根据形参【类型】列表来获取指定的一个构造器的
        Constructor<?> constructors1 = clazz.getDeclaredConstructor(String.class);
        constructors1.setAccessible(true);
        Object obj1 = constructors1.newInstance("today1");
        System.out.println(obj1);//Person{name='today1', age=0}

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

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

//1.实现的全部接口 
public Class<?>[] getInterfaces() 
//确定此对象所表示的类或接口实现的接口。
//2.所继承的父类 
public Class<? Super T> getSuperclass() 
//返回表示此 Class 所表示的实体(类、接口、基本类型)的父类的 Class。
//3.全部的构造器 
public Constructor<T>[] getConstructors() 
//返回此 Class 对象所表示的类的所有public构造方法。 
public Constructor<T>[] getDeclaredConstructors() 
//返回此 Class 对象表示的类声明的所有构造方法。 
//Constructor类中: 
//取得修饰符: 
public int getModifiers(); 
//取得方法名称: 
public String getName(); 
//取得参数的类型: 
public Class<?>[] getParameterTypes(); 
//4.全部的方法 
public Method[] getDeclaredMethods() 
//返回此Class对象所表示的类或接口的全部方法 
public Method[] getMethods() 
//返回此Class对象所表示的类或接口的public的方法 

//Method类中: 
public Class<?> getReturnType() 
//取得全部的返回值 
public Class<?>[] getParameterTypes() 
//取得全部的参数 
public int getModifiers() 
//取得修饰符 
public Class<?>[] getExceptionTypes() 
//取得异常信息 

//5.全部的Field 
public Field[] getFields() 
//返回此Class对象所表示的类或接口的public的Field。 
public Field[] getDeclaredFields() 
//返回此Class对象所表示的类或接口的全部Field。 

//Field方法中: 
public int getModifiers() 
//以整数形式返回此Field的修饰符 
public Class<?> getType() 
//得到Field的属性类型 
public String getName() 
//返回Field的名称。

//6. Annotation相关 
get Annotation(Class<T> annotationClass) getDeclaredAnnotations() 
//7.泛型相关 
//获取父类泛型类型: 
Type getGenericSuperclass() 
//泛型类型:
ParameterizedType 
//获取实际的泛型类型参数数组: 
getActualTypeArguments() 
//8.类所在的包 
Package getPackage()
public class UseTest{
    /*
    获取当前运行时类的属性结构
     */
    @Test
    public void test(){
        Class clazz = Person1.class;
        //获取属性结构
        //getFields():获取当前运行时类及其父类中声明为public访问权限的属性
        Field[] fields = clazz.getFields();
        for (Field f :
                fields) {
            System.out.println(f);
            //public int SE.Reflection.Person1.id
            //public double SE.Reflection.Creature.weight
        }
        System.out.println("----------------");
        //getDeclaredFields():获取当前运行时类中声明的所有属性。(不包含父类中声明的属性
        Field[] declaredFields = clazz.getDeclaredFields();
        for (Field f :
                declaredFields) {
            System.out.println(f);
            //private java.lang.String SE.Reflection.Person1.name
            //int SE.Reflection.Person1.age
            //public int SE.Reflection.Person1.id
        }
    }

    //权限修饰符  数据类型  变量名
    @Test
    public void test2(){
        Class clazz = Person1.class;
        Field[] declaredFields = clazz.getDeclaredFields();
        for (Field f :declaredFields) {
            //1、获取权限修饰符
            int modifiers = f.getModifiers();
            System.out.print(Modifier.toString(modifiers)+"\t");

            //2、数据类型
            Class type = f.getType();
            System.out.print(type.getName()+"\t");
            //class java.lang.String	type
            //java.lang.String          type.getName()

            //3、变量名
            String fName = f.getName();
            System.out.println(fName);
            System.out.println();
        }
    }

    /*
    获取运行时类的方法结构
     */
    @Test
    public void test3(){
        Class clazz= Person1.class;
        
        //getMethods():获取当前运行时类及其父类中声明为public权限的方法
        Method[] methods = clazz.getMethods();
        for (Method m :
                methods) {
            System.out.println(m);
            //public int SE.Reflection.Person1.compareTo(java.lang.Object)
            //public int SE.Reflection.Person1.compareTo(java.lang.String)
            //public void SE.Reflection.Person1.info()
            //public java.lang.String SE.Reflection.Person1.display(java.lang.String)
            //public void SE.Reflection.Creature.eat()
            //public final void java.lang.Object.wait() throws java.lang.InterruptedException
            //public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
            //public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
            //public boolean java.lang.Object.equals(java.lang.Object)
            //public java.lang.String java.lang.Object.toString()
            //public native int java.lang.Object.hashCode()
            //public final native java.lang.Class java.lang.Object.getClass()
            //public final native void java.lang.Object.notify()
            //public final native void java.lang.Object.notifyAll()
        }
        System.out.println("----------------");

        //getDeclaredMethods():获取当前运行时类中声明的所有方法。(不包含父类中声明的方法
        Method[] declaredMethods = clazz.getDeclaredMethods();
        for (Method m :
                declaredMethods) {
            System.out.println(m);
            //public int SE.Reflection.Person1.compareTo(java.lang.Object)
            //public int SE.Reflection.Person1.compareTo(java.lang.String)
            //public void SE.Reflection.Person1.info()
            //public java.lang.String SE.Reflection.Person1.display(java.lang.String)
            //private java.lang.String SE.Reflection.Person1.show(java.lang.String)
        }
    }

    /*
    @Xxx
    权限修饰符 返回值类型 方法名(参数类型1,形参名1...) throws XxxException{}
     */
    @Test
    public void test4(){
        Class clazz = Person1.class;
        //1、获取方法声明的注解
        Method[] declaredMethods = clazz.getDeclaredMethods();

        for (Method method :
                declaredMethods) {
            Annotation[] annotations = method.getAnnotations();
            for (Annotation a :annotations) {
                System.out.print(a);
                //@SE.Reflection.MyAnnotation(value=hello)
                //@SE.Reflection.MyAnnotation(value=today is Monday)
            }

            //2、每个方法的权限修饰符
            System.out.print(Modifier.toString(method.getModifiers())+"\t");

            //3、返回值类型
            System.out.print(method.getReturnType().getName()+"\t");

            //4、方法名
            System.out.print(method.getName());
            System.out.print("(");

            //5、形参列表
            Class[] parameterTypes = method.getParameterTypes();
            if(!(parameterTypes==null&&parameterTypes.length==0)){
               for (int i=0;i<parameterTypes.length;i++){
                   if(i==parameterTypes.length-1){
                       System.out.print(parameterTypes[i].getName()+"args_"+i);
                       break;
                   }
                    System.out.print(parameterTypes[i].getName()+"args_"+i+",");
                }
            }
            System.out.print(")");

            //6、抛出的异常
            Class[] exceptionTypes = method.getExceptionTypes();
            if(exceptionTypes.length>0){
                for (int i = 0; i < exceptionTypes.length; i++) {
                    System.out.print("throws ");
                    if(i==exceptionTypes.length-1){
                        System.out.print(exceptionTypes[i].getName());
                        break;
                    }
                    System.out.print(exceptionTypes[i].getName()+",");
                }
            }
            System.out.println();
// public volatile	int	compareTo(java.lang.Objectargs_0)
//public	int	compareTo(java.lang.Stringargs_0)
//public	void	info()throws java.lang.ExceptionInInitializerError,throws java.lang.NumberFormatException
//@SE.Reflection.MyAnnotation(value=today is Monday)private	java.lang.String	show(java.lang.Stringargs_0)
//@SE.Reflection.MyAnnotation(value=hello)public	java.lang.String	display(java.lang.Stringargs_0)throws java.lang.NullPointerException
        }

    }

    /*
    获取构造器结构
     */
    @Test
    public void test5(){
        Class clazz = Person1.class;
        //getConstructors():当前运行时类中声明为public的构造器
        Constructor[] constructors = clazz.getConstructors();
        for (Constructor c :
                constructors) {
            System.out.println(c);
           //public SE.Reflection.Person1()
        }
        System.out.println();

        //getDeclaredConstructors():获取当前运行时类声明的所有的构造器
        Constructor[] declaredConstructors = clazz.getDeclaredConstructors();
        for (Constructor c:declaredConstructors) {
            System.out.println(c);
            //SE.Reflection.Person1(java.lang.String,int)
            //private SE.Reflection.Person1(java.lang.String)
            //public SE.Reflection.Person1()
        }
    }

     /*
        获取运行时类的带泛型的父类
         */
    @Test
    public void test6(){
        Class clazz = Person1.class;

        Type genericSuperclass = clazz.getGenericSuperclass();
        System.out.println(genericSuperclass);
        //SE.Reflection.Creature<java.lang.String>

        //获取运行时类的带泛型的父类的泛型
       ParameterizedType paramType = (ParameterizedType) genericSuperclass;
       //1获取泛型类型
        Type[] actualTypeArguments = paramType.getActualTypeArguments();
        for (Type t :
                actualTypeArguments) {
            //System.out.println(t.getTypeName());
            // java.lang.String

            System.out.println(((Class) t).getName());
            //java.lang.String
        }
    }

    /*
    获取运行时类实现的接口
     */
    @Test
    public void test7(){
        Class clazz = Person1.class;
        Class[] interfaces = clazz.getInterfaces();
        for (Class c :
                interfaces) {
            System.out.println(c);
            //interface java.lang.Comparable
            //interface SE.Reflection.MyInterface
        }
        System.out.println();

        //获取运行时类的父类实现的接口
        Class[] interfaces1 = clazz.getSuperclass().getInterfaces();
        for (Class c :
                interfaces1) {
            System.out.println(c);
            //interface java.io.Serializable
        }
    }

    /*
    获取运行时类所在的包
     */
    @Test
    public void test8(){
        Class clazz = Person1.class;
        Package pack = clazz.getPackage();
        System.out.println(pack);
        //package SE.Reflection
    }

    /*
    获取运行时类声明的注解
     */
    @Test
    public void test9(){
        Class clazz = Person1.class;
        Annotation[] annotations = clazz.getAnnotations();
        for (Annotation a :
                annotations) {
            System.out.println(a);
            //@SE.Reflection.MyAnnotation(value=hi)
        }
    }
}

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

public class UseTest1 {
    /*
    如何操作运行时类中指定的属性---掌握
     */
    @Test
    public void testField() throws NoSuchFieldException, InstantiationException, IllegalAccessException {
        Class clazz = Person1.class;
        //创建运行时类的对象
        Person1 p = (Person1) clazz.newInstance();
        //获取指定属性:要求运行时类中属性声明为public
        //通常不采用
        Field id = clazz.getField("id");
        /*
        设置当前属性的值
        set():参数1:指明设置哪个对象的属性 参数2:将此属性值设置为多少
         */
        id.set(p, 11001);

        /*
        获取当前属性的值
        get():参数1:获取哪个对象的当前属性值
         */
        int pid = (int) id.get(p);
        System.out.println(pid);

        System.out.println("------------------");
        // 1\getDeclaredField(String fieldname):获取当前运行时类中指定变量名的属性
        Field name = clazz.getDeclaredField("name");

        //2\保证当前属性是可访问的
        name.setAccessible(true);
        //3\获取、设置指定对象的此属性值
        name.set(p, "forget");
        System.out.println(name.get(p));
    }

    /*
    如何操作运行时类中指定的方法
     */
    @Test
    public void testMethod() throws Exception {
        Class clazz = Person1.class;
        //创建运行时类的对象
        Person1 p = (Person1) clazz.newInstance();

        /*
        1、获取指定的某个方法
        getDeclaredMethod():参数1:指明获取方法的名称 参数2:指明获取的方法的形参列表
         */
        Method show = clazz.getDeclaredMethod("show", String.class);

        //保证当前方法是可访问的
        show.setAccessible(true);

        /*
        2、调用方法invoke():参数1:方法的调用者  参数2:给方法形参赋值的实参
        invoke()的返回值即为对应类中调用的方法的返回值
         */
        Object returnValue = show.invoke(p, "china"); //我的国籍是:china
        //String nation = show.invoke(p, "china");
        System.out.println(returnValue);//china

        System.out.println("-------如何调用静态方法------");
        // private static void showDesc()

        Method showDesc = clazz.getDeclaredMethod("showDesc");
        showDesc.setAccessible(true);
        //如果调用的运行时类中的方法没有返回值,则此invoke()返回null
        Object returnValue1 = showDesc.invoke(null);
        //Object returnValue1 = showDesc.invoke(Person1.class);
        System.out.println(returnValue1);//null
    }
    /*
    如何调用运行时类中指定的构造器
     */
    @Test
    public void testConstructor() throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        Class clazz = Person1.class;

        //private Person1(String name)
        /*
        1\获取指定的构造器
        getDeclaredConstructor():参数:指明构造器的参数列表
         */
        Constructor constructor = clazz.getDeclaredConstructor(String.class);

        //2\保证此构造器是可访问的
        constructor.setAccessible(true);
        //3\调用此构造器创建运行时类的对象
        Person1 per = (Person1) constructor.newInstance("Tom");
        System.out.println(per);
        //Person1{name='Tom', age=0, id=0}
    }

}

补充:

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

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

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


批量插入

批量执行SQL语句

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

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

 public void testInsert() throws SQLException, ClassNotFoundException {
        /**
         * 使用批量插入的方式插入10000条数据
         * 1、路径后添加rewriteBatchedStatements=true,允许批量插入
         * 2、insert into values 必须写,且语句不能添加;结束
         * 3、不是执行语句,是批量添加,addBatch()
         * 4、遍历添加 完毕以后,统一批量执行executeBatch()
         */
        Class.forName("com.mysql.cj.jdbc.Driver");
        Connection connection = DriverManager.getConnection("jdbc:mysql:///web?rewriteBatchedStatements=true", "root", "1234567");
        String sql = "insert into user(account,password,nickname) values(?,?,?)";
        PreparedStatement preparedStatement = connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);

        long start = System.currentTimeMillis();
        for (int i = 0; i < 10000; i++) {
            preparedStatement.setObject(1, "w"+i);
            preparedStatement.setObject(2, "1s"+i);
            preparedStatement.setObject(3, "xs"+i);
            preparedStatement.addBatch();//不执行,接到values后面
        }
        preparedStatement.executeBatch();//执行批量操作

        long end = System.currentTimeMillis();

        System.out.println("执行10000次数据插入消耗时间:"+(end-start));

        preparedStatement.close();
        connection.close();
    }

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

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

 数据源连接池

工作原理

多种开源的数据库连接池

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

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

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

public class DruidUsePart {
    /**
     * 直接使用代码设置连接池连接参数方式
     * 1、创建一个druid连接对象
     * 2、设置连接池参数【必须|非必须】
     * 3、获取连接【通用方法,所有连接池都一样】
     * 4、回收连接【通用方法
     */

    public void testHard() throws SQLException {
        //连接池对象
        DruidDataSource druidDataSource = new DruidDataSource();
        //设置参数
        //必须 连接数据库驱动类的全限定符【注册驱动】 | url | user | password
        druidDataSource.setUrl("jdbc:mysql:///jdbc");
        druidDataSource.setUsername("root");
        druidDataSource.setPassword("1234567");
        druidDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        //非必须 初始化连接数量,最大的连接数量
        druidDataSource.setInitialSize(4);//初始化连接数量
        druidDataSource.setMaxActive(19);//最大数量

        //获取连接
        Connection connection = druidDataSource.getConnection();
        //数据库curd

        //回收连接
        connection.close();//连接池提供的连接,close,就是回收连接

    }

   
}

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

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

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

 /**
     * 通过读取外部配置文件的方法,实例化druid连接池的对象
     */
    @Test
    public void testSoft() throws Exception {
        //1、读取外部的配置文件
        Properties properties = new Properties();

        //src下的文件,可以使用类加载器提供的方法
        InputStream resourceAsStream = DruidUsePart.class.getClassLoader().getResourceAsStream("druid.properties");
        properties.load(resourceAsStream);

        //2、使用连接池的工具类的工厂模式,创建连接池
        DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
        Connection connection = dataSource.getConnection();
        //数据库curd操作在创建连接后的这里实现即可

        connection.close();

    }

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

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

public class DruidTest {
    private static DataSource dataSource=null;
    static {
        try {
            //1.加载配置文件
            Properties properties = new Properties();
            InputStream resourceAsStream = DruidTest.class.getClassLoader().getResourceAsStream("jdbc.properties");
            properties.load(resourceAsStream);
            //2.获取DataSource
            dataSource = DruidDataSourceFactory.createDataSource(properties);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    //获取连接
    public static Connection getConnection() throws Exception {
        return dataSource.getConnection();
    }

    //关闭资源
    public static void close(Connection connection, PreparedStatement preparedStatement){
        close(connection,preparedStatement,null);
    }

    public static void close(Connection connection, PreparedStatement preparedStatement, ResultSet resultSet){
        try {
            if(resultSet!=null){
                resultSet.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        try {
            if(preparedStatement!=null){
                preparedStatement.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        try {
            if(connection!=null){
                connection.close();
            }
        } catch (Exception e) {
           e.printStackTrace();
        }
    }

}

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结果集处理

例如测试添加

public void testInsert() {
        Connection connection=null;
        try {
            QueryRunner queryRunner = new QueryRunner();
            connection= DruidTest.getConnection();
            String sql = "insert into tb_student(sid,sname,gender,age)values(?,?,?,?)";
            int update = queryRunner.update(connection, sql, 2, "forget", "女", 19);
            System.out.println(update);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }finally {
            DruidTest.close(connection,null);
        }
    }

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

public void testDetele() {
        Connection connection=null;
        try {
            QueryRunner queryRunner = new QueryRunner();
            connection= DruidTest.getConnection();
            String sql = "delete from tb_student where sid = ?";
            int update = queryRunner.update(connection, sql, 2);
            System.out.println(update);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }finally {
            DruidTest.close(connection,null);
        }
    }
}

使用ResultSetHandler结果集处理

ResultSetHandler接口及实现类

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

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

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

 public void testSelect1() {
        Connection connection=null;
        try {
            QueryRunner queryRunner = new QueryRunner();
            connection= DruidTest.getConnection();
            String sql = "select * from tb_student where sid = ?";
            BeanHandler<Student> handler = new BeanHandler<>(Student.class);
            Student query = queryRunner.query(connection, sql, handler, 1);
            System.out.println(query);

        } catch (Exception e) {
            throw new RuntimeException(e);
        }finally {
            DruidTest.close(connection,null);
        }
    }

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

public void testSelect2() {
        Connection connection=null;
        try {
            QueryRunner queryRunner = new QueryRunner();
            connection= DruidTest.getConnection();
            String sql = "select * from tb_student";
            BeanListHandler<Student> handler = new BeanListHandler<>(Student.class);
            List<Student> query = queryRunner.query(connection, sql, handler);
            System.out.println(query);

        } catch (Exception e) {
            throw new RuntimeException(e);
        }finally {
            DruidTest.close(connection,null);
        }
    }

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

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

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

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

自定义ResultSetHandler的实现类

public void testSelect6() {
        Connection connection=null;
        try {
            QueryRunner queryRunner = new QueryRunner();
            connection= DruidTest.getConnection();
            String sql = "select * from tb_student";
            ResultSetHandler<List<Student>> handler = new ResultSetHandler<List<Student>>() {

                @Override
                public List<Student> handle(ResultSet resultSet) throws SQLException {
                    List<Student> list = new ArrayList<>();
                    System.out.println("自定义");
                    while(resultSet.next()){
                        int sid = resultSet.getInt(1);
                        String sname = resultSet.getString(2);
                        String gender = resultSet.getString(3);
                        int age = resultSet.getInt(4);
                        list.add(new Student(sid,sname,gender,age));
                    }
                    return list;
                }

            };
            List<Student> query = queryRunner.query(connection, sql, handler);
           query.forEach(System.out::println);

        } catch (Exception e) {
            throw new RuntimeException(e);
        }finally {
            DruidTest.close(connection,null);
        }
    }

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

输出结果:

数据库事务

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

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

创建表

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

public class BankDao {
    /**
     * 加钱的数据库操作方法(jdbc)
     *     account 加钱的账号
     *     money 加钱的金额
     *
     */

    public void add(String account,int money,Connection connection) throws ClassNotFoundException, SQLException {

        String sql = "update bank set money = money+? where account =?; ";

        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        preparedStatement.setObject(1,money);
        preparedStatement.setObject(2,account);
        preparedStatement.executeUpdate();
        System.out.println("加钱成功!");
    }

    /**
     * 减钱的数据库操作方法(jdbc)
     *     account 减钱的账号
     *     money 减钱的金额
     *
     */

    public void sub(String account,int money,Connection connection) throws ClassNotFoundException, SQLException {
        String sql = "update bank set money = money-? where account = ?; ";

        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        preparedStatement.setObject(1,money);
        preparedStatement.setObject(2,account);
        preparedStatement.executeUpdate();
        System.out.println("减钱成功!");

    }
}
public class BankService {
    @Test
    public void start() throws SQLException, ClassNotFoundException {
        transfer("小熊", "大大", 500);
    }

    /*
    事务添加是在业务方法中:
    利用try catch代码块,开启事务和提交事务,事务回滚
    将connection传入dao层即可,dao只负责使用,不要close()
     */
    public void transfer(String addAccount, String subAccount, int money) throws SQLException, ClassNotFoundException {
        BankDao bankDao = new BankDao();
        Class.forName("com.mysql.cj.jdbc.Driver");
        Connection connection = DriverManager.getConnection("jdbc:mysql:///jdbc", "root", "1234567");

        try {
            //开启事务
            connection.setAutoCommit(false);

            //执行数据库动作
            bankDao.add(addAccount, money, connection);
            System.out.println("----------------------");
            bankDao.sub(subAccount, money, connection);

            //事务提交
            connection.commit();
        } catch (Exception e) {
            // 5.若有异常,则回滚事务
            try {
                connection.rollback();
            } catch (SQLException e1) {
                e1.printStackTrace();
            }
            e.printStackTrace();
        } finally {
            try {
                //6.恢复每次DML操作的自动提交功能
                connection.setAutoCommit(true);
            } catch (SQLException e) {
                e.printStackTrace();
            }
               //7.关闭连接
            connection.close();
        }
    }
}

执行后得到

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

DAO及相关实现类

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

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

忘记578

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值