栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

JDBC学习笔记(2)

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

JDBC学习笔记(2)

概述

Java中的数据存储技术

JDBC介绍

 

 

 JDBC提供了接口也就是规范,具体实现细节由具体公司提供驱动。

为什么要定义规范,如果没有规范,就添加数据命令而言,Mysql可能定义为add,而Oracle定义为insert,SQLServer定义为put,还有可能每个方法传入的参数个数不同,操作的步骤数不同,就导致用Java操作不同数据库时,代码通用性低。

SUN公司提供这套规范,让各个公司去实现实现类,这些实现类的集合就是JDBC驱动。

JDBC体系结构

 JDBC编写步骤

 Java.sql包在jdk中有。

驱动包手动需要导入,根据不同数据库系统导入不同的包。

创建Connection。就像操作数据库客户端时也要先连接数据库,这里不用客户端操作数据库也要连接。

 Statement去帮助执行SQL。有查询和增删改。

ResultSet,当执行的是查询就需要有结果集对象了。

ODBC是微软提供的操作不同数据库的API。先连接JDBC再连接ODBC再注册驱动的一种方式。

URL 

URL就是定位到某一个数据库。

在网上定位一个具体资源时的URL格式:http://localhost:8080/具体资源。

这里jdbc:mysql://就相当于http://。

获取数据库连接

因为JDBC是用来定义规范的,所以JDBC的API中大部分都是接口。 

获取连接的四种方式  方式1:
public class JDBC_Demo1 {
    public static void main(String[] args) throws SQLException {
        //获取驱动,也就是获取数据库系统厂商提供的jar包。
        Driver driver = new com.mysql.cj.jdbc.Driver();
        //定位到数据库
        String url = "jdbc:mysql://localhost:3306/demo";
        //将用户名和密码封装在Properties中
        Properties info = new Properties();
        info.setProperty("user","root");
        info.setProperty("password","niit");
        Connection connection = driver.connect(url,info);
        System.out.println(connection);
    }
    
}

方式2:

public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, SQLException {
        //方式2是对方式1的迭代
        //为了让程序有更好的可移植性,要减少第三方程序的出现,
        //比如new com.mysql.cj.jdbc.Driver()就是第三方


        //1.获取Driver实现类对象,使用反射
        //反射机制指的是程序在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法
        Class clazz = Class.forName("com.mysql.cj.jdbc.Driver");
        Driver driver = (Driver) clazz.newInstance();

        //2.提供要连接的数据库
        String url = "jdbc:mysql://localhost:3306/demo";

        //3.提供连接需要的用户名和密码
        Properties info = new Properties();
        info.setProperty("user","root");
        info.setProperty("password","niit");

        //4.获取连接
        Connection connection = driver.connect(url,info);

        System.out.println(connection);

    }
}

方式3:

public class JDBC_Demo3 {
    //方式3:使用DriverManager替换Driver
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, SQLException {
        //获取Deriver实现类对象
        Class clazz = Class.forName("com.mysql.cj.jdbc.Driver");
        Driver driver = (Driver) clazz.newInstance();
        //注册驱动
        DriverManager.registerDriver(driver);

        String url = "jdbc:mysql://localhost:3306/demo";
        String user = "root";
        String password = "niit";
        //获取连接
        DriverManager.getConnection(url,user,password);
    }
    }

方式4:可以只是加载驱动,不用显式的注册驱动了

public class JDBC_Demo4 {
    @Test
    public void testConnection() throws ClassNotFoundException, SQLException {
        //方式3的优化
        //1.三个基本信息
        String url = "jdbc:mysql://localhost:3306/demo";
        String user = "root";
        String password = "niit";
        //2.获取Driver实现类对象
        //注册驱动这个活Driver中静态代码块做了,静态代码块随着类的加载而执行,
        Class.forName("com.mysql.cj.jdbc.Driver");

        //3.获取连接
        Connection connection = DriverManager.getConnection(url,user,password);
        System.out.println(connection);
    }
}

为什么可以省略注册驱动?

利用反射获取Driver对象时,随着类的加载执行了静态代码块中的代码。

 自动注册了驱动,注册驱动理解为具体和哪家数据库提供的类文件对接。

注册了驱动就等于准备好了操作数据库的工具,就可以连接数据库进行操作了。 

方式5:直接连接
public class JDBC_Demo4 {
    @Test
    public void testConnection() throws ClassNotFoundException, SQLException {
        //方式3的优化
        //1.三个基本信息
        String url = "jdbc:mysql://localhost:3306/demo";
        String user = "root";
        String password = "niit";
        //2.获取Driver实现类对象
        //注册驱动这个活Driver中静态代码块做了,静态代码块随着类的加载而执行,
        //Class.forName("com.mysql.cj.jdbc.Driver");

        //3.获取连接
        Connection connection = DriverManager.getConnection(url,user,password);
        System.out.println(connection);
    }
}

连接时,自动获取对象,并且注册驱动。

但不推荐这样,这样适用于MySQL,但不一定适用于别的数据库。 

 方式6:最终版,基本信息暴露在代码中不安全。
public class JDBC_Demo5 {
    public static void main(String[] args) throws ClassNotFoundException, IOException, SQLException {
        //properties读取路径默认是从当前module的src下
        InputStream is = JDBC_Demo5.class.getClassLoader().getResourceAsStream("connection\jdbc.properties");
        Properties properties = new Properties();
        properties.load(is);
        String user = properties.getProperty("user");
        String password = properties.getProperty("password");
        String url = properties.getProperty("url");
        String driverClass = properties.getProperty("driverClass");

        Class.forName(driverClass);
        Connection connection = DriverManager.getConnection(url,user,password);
        System.out.println(connection);

    }
}

将数据库连接需要的基本信息放入配置文件中,通过读取配置文件的方式获取连接。

好处:

1.实现了数据与代码的分离,实现了解耦。

解耦的好处比如想要改这些数据可以在配置文件中修改,数据只在配置文件中,则代码和数据分开后,各自的可读性都提高。如果数据嵌套在代码中,如果一个不了解这些代码的人来修改数据就会比较麻烦,而让他直接去修改配置文件就会比较简单。

2.后期项目需要配置到服务器时需要打包,如果需要修改配置文件信息,可以避免程序重新打包。如果数据在程序中如果要更改数据就要对这个Java文件重新打包。

封装连接和关闭资源
public class JDBCUtils {
    //静态方法不需要获取对象
    public static Connection getConnection(){
        try{
            //加载配置文件
            InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc.properties");
            Properties properties = new Properties();
            properties.load(is);

            //获取基本信息
            String url = properties.getProperty("url");
            String user = properties.getProperty("user");
            String password = properties.getProperty("password");
            String driverClass = properties.getProperty("driverClass");
            //与数据库连接
            Class.forName(driverClass);
            Connection connection = DriverManager.getConnection(url, user, password);
            return connection;
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }

    //关闭资源
    public static void closeResource(Connection connection, Statement ps,ResultSet resultSet){
        try{
            if(connection!=null)
                connection.close();
            if(ps!=null)
                ps.close();
            if(resultSet!=null)
                resultSet.close();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}
user=root
password=niit
url=jdbc:mysql://localhost:3306/demo
driverClass=com.mysql.cj.jdbc.Driver

使用PreparedStatement实现CRUD操作

CRUD:Create,Retrieve,Update,Delete   增删改查

介绍

操作和访问数据库 

 Statement操作数据表的弊端

需要拼写SQL语句,并且有SQL注入的风险。

补充:用scanner输入时,用sc.next()输入时遇到空格或换行结束,用sc.nextLine时只以换行为结束。

使用PreparedStatement

PreparedStatement是Statement的子接口。

一般的PreparedStatement操作

public class TestPreparedStatement {
    public static void main(String[] args) throws IOException, ClassNotFoundException, SQLException {
        //加载配置文件
        InputStream is =  TestPreparedStatement.class.getClassLoader().getResourceAsStream("jdbc.properties");
        Properties properties = new Properties();
        properties.load(is);

        //获取基本信息
        String url = properties.getProperty("url");
        String user = properties.getProperty("user");
        String password = properties.getProperty("password");
        String driverClass = properties.getProperty("driverClass");
        //与数据库连接
        Class.forName(driverClass);
        Connection connection = DriverManager.getConnection(url,user,password);

        //操作数据库
        //预编译sql语句,返回PreparedStatement对象
        String sql = "insert into demo(userName,age) values(?,?)";//?是占位符
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        //填充占位符
        preparedStatement.setString(1,"小猪");
        preparedStatement.setInt(2,18);//日期型用setDate

        //执行sql命令
        preparedStatement.execute();

        //资源的关闭
        //为了避免空指针异常
        if(preparedStatement!=null)
        preparedStatement.close();
        if (connection!=null)
        connection.close();
    }
    //增删改
    //向demo表中修改数据
}

通用的增删改操作

public class Test2 {
    public static void main(String[] args) throws SQLException, IOException, ClassNotFoundException {
        //建议表名上加一个`,着重符,在tab键上面,假如表面是关键字,就会报错,加上`就可以避免
        String sql = "delete from `demo` where age = ?";
        update(sql,18);
    }

    //通用增删改操作
    public static void update(String sql,Object ...args){//可变形参
       try{
           //获取连接
           Connection connection = JDBCUtils.getConnection();
           //预编译sql
           PreparedStatement preparedStatement = connection.prepareStatement(sql);
           //填充占位符
           //遍历可变数组,数组的内容就是传进来填写占位符的数据
           for (int i = 0;i < args.length;i++){
               preparedStatement.setObject(i+1,args[i]);
           }
           //执行
           preparedStatement.execute();
           //关闭资源
           JDBCUtils.closeResource(connection,preparedStatement,null);
       }catch (Exception e){
           e.printStackTrace();
       }
    }
}

增删改的操作不需要返回结果,只需要将占位符填写就可以。所以不同的增删改操作只是填写的占位符的个数和值不同。

一般的查询操作

//查询
public class Test3 {

    @Test
    public void testQuery() throws Exception{
        Connection connection = JDBCUtils.getConnection();
        String sql = "select * from demo where age = ?";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        preparedStatement.setInt(1,21);
        //执行并返回结果集
        ResultSet resultSet = preparedStatement.executeQuery();
        //处理结果集
        //next功能:判断下面是否有元素,有的话下移,没有的话不下移,说明有两个功能,第一个是判断,第二个是移动
        if(resultSet.next()){
            //获取当前一行数据的各个字段值
            String name = resultSet.getString(1);
            int age = resultSet.getInt(2);
            //将数据封装为一个对象输出查询结果
            System.out.println(new User(name, age));
        }
        JDBCUtils.closeResource(connection,preparedStatement,resultSet);
    }
}

 next的作用:1.判断下面是否有数据返回true或false。 2.如果有数据就下移,如果没数据就不下移。

这里用到了ORM思想

Java属性类型与MySQL字段类型对应

 

 日期型如何传值。

针对于一个表的通用查询操作

//针对某个表的通用的查询,针对于查询时需要查询的字段个数不同的通用
//不同的表需要的JavaBean属性不同,所以是针对同一个表的通用查询
//select 字段1,字段2... from table where 字段 = ?
public class Test4 {

    @Test
    public void test(){
        //JavaBean的属性名和字段名要相同,因为要根据属性名要给该域赋值,这个属性名是从结果集的列名上获取
        String sql = "select userName,age from demo where age = ?";
        System.out.println(testQuery(sql, 21));
    }

    public User testQuery(String sql,Object ...args){
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;
        try{
            //获取连接
            connection = JDBCUtils.getConnection();
            preparedStatement = connection.prepareStatement(sql);
            //填写占位符
            for(int i = 0;i < args.length;i++){
                preparedStatement.setObject(i+1,args[i]);
            }
            //执行并返回结果集
            resultSet = preparedStatement.executeQuery();

            //对结果集的一条结果进行封装
            //获取结果集的列数,从而决定取的时候取多少次
            //获取结果集的元数据,修饰现有数据的数据
            ResultSetmetaData metaData = resultSet.getmetaData();
            //获取列数
            int columnCount = metaData.getColumnCount();

            if(resultSet.next()){
                User user = new User();
                //取的时候取多少次由要查询的字段的个数决定
                for(int i = 0;i 
 

为什么是针对一个表的通用查询操作?因为不同的表的字段不同,查询一个表时,除了填写占位符,前面要查询的字段也是变量。

将结果集封装到JavaBean对象中,JavaBean对象的属性名称要和字段名称相同,因为利用反射时,是根据列名在对象中查找属性的域然后赋值。

但是因为数据库和Java中取名习惯不同,如Java中的orderID,在数据库一般取为order_id,这里就需要一步转换。

先将查询的字段起别名:select order_id orderID from demo where age = ?

然后将String columnName = metaData.getColumnName(i + 1);改为String columnLabel = metaData.getColumnLabel(i + 1);

getColumnLabel是返回结果集的别名,如果没有别名则返回列名,所以推荐使用这个方法。

针对于不同表的通用查询操作-不同表返回一个结果 

public class Test5 {
    @Test
    public void test(){
        String sql = "select userName from demo where age = ?";
        User user = getInstance(User.class,sql,21);
        System.out.println(user);
    }

    //sql是查询语句,args填充占位符
    //Class这个T是运行实例
    // T,T是返回值类型,声明泛型方法
    public T getInstance(Class clazz,String sql,Object ...args){
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;
        try{
            //获取连接
            connection = JDBCUtils.getConnection();
            //预编译sql
            preparedStatement = connection.prepareStatement(sql);
            //填写占位符
            for(int i = 0;i < args.length;i++){
                preparedStatement.setObject(i+1,args[i]);
            }
            //执行并返回结果集
            resultSet = preparedStatement.executeQuery();


            ResultSetmetaData metaData = resultSet.getmetaData();
            //获取列数
            int columnCount = metaData.getColumnCount();

            if(resultSet.next()){
                //将传入的类创建实例
                T t = clazz.newInstance();
                //取的时候取多少次由要查询的字段的个数决定
                for(int i = 0;i 
 

用到了反射和泛型。前提都是需要先将JavaBean先创建好。

针对不同表的通用查询操作-返回多个结果

public class Test6 {

    @Test
    public void test(){
        String sql = "select userName from demo where age = ?";
        List list = getForList(User.class, sql, 21);
        //遍历集合
        list.forEach(System.out::println);
    }

    public  List getForList(Class clazz, String sql, Object ...args){
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;
        try{
            //获取连接
            connection = JDBCUtils.getConnection();
            //预编译sql
            preparedStatement = connection.prepareStatement(sql);
            //填写占位符
            for(int i = 0;i < args.length;i++){
                preparedStatement.setObject(i+1,args[i]);
            }
            //执行并返回结果集
            resultSet = preparedStatement.executeQuery();


            ResultSetmetaData metaData = resultSet.getmetaData();
            //获取列数
            int columnCount = metaData.getColumnCount();
            //创建集合
            List list = new ArrayList();
            while (resultSet.next()){
                //将传入的类创建实例
                T t = clazz.newInstance();
                //处理每一行数据的每一列,给每一行看作一个对象,给字段(属性)赋值
                for(int i = 0;i 
 

只在过滤条件处填充占位符。

PreparedStatement的优点

1.解决了sql拼串问题。

2.解决了sql注入问题。

 假如用statement语句去执行这条sql命令,过滤条件会判断为or的关系

而用占位符代替之后,预编译会认为过滤条件是且的关系,之后只填充占位符的内容,如果该内容中有or也无法改变原先且的关系,这就是预编译的好处,所以防止了SQL注入。

3.PreparedStatement操作Blob数据,而Statement做不到

4.PreparedStatement可以实现更高效的批量操作。

假如要添加一万条数据,每条数据都不同,如果用Statement的话,每条数据都要校验格式等,就需要重复一万次,而用预编译的话,由于每条数据的格式都相同,只是占位符的内容不同,所以只需要一次。以后只往占位符填充内容就ok了。

小结

1.在操作数据库时,只需要面向JDBC这套接口编程。

在用到操作数据库的包中,导入的都是接口,都没有引入第三方的API(也就是各个数据库厂商提供的jar包),那么引用这些jar包的?

 通过拿着这个Driver去获得具体的连接。这个connection就是MySQL的连接。这个connection利用了多态,表面上返回了一个接口,其实这个connection是一个MySQL具体实现类的对象。表面上都是使用的接口,实际上都是具体的MySQL实现类对象。

操作BLOB类型字段 介绍BLOB类型

BLOB=binary large object, 可以用BLOB类型存图片,视频等。

对BLOB数据的操作 向数据库中存BLOB数据
//向数据库中存BLOB数据
public class BlobTest1 {

    @Test
    public void test() throws Exception{
        Connection connection = JDBCUtils.getConnection();
        String sql = "insert into blobtable values (?,?)";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        preparedStatement.setObject(1,1);
        //这个fis就代表了这张图片
        FileInputStream fis = new FileInputStream(new File("C:\Users\24213\Pictures\Camera Roll\little cat.webp"));
        preparedStatement.setBlob(2,fis);
        preparedStatement.execute();
        JDBCUtils.closeResource(connection,preparedStatement,null);
    }
}

向数据库中查找BLOB数据并将数据保存到本地
public class BlobTest2 {
    //查找BLOB数据
    @Test
    public void test()throws Exception{
        Connection connection = JDBCUtils.getConnection();
        String sql = "select photo from blobtable where id = ?";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        preparedStatement.setInt(1,1);
        ResultSet resultSet = preparedStatement.executeQuery();
        
        InputStream is = null;
        FileOutputStream fos = null;
        
        if(resultSet.next()){
            //将Blob类型的字段数据提取出来
            //可以定义成员变量存储,也可以存储到对象中
            Blob photo = resultSet.getBlob("photo");
            //保存到本地
            is = photo.getBinaryStream();
            fos = new FileOutputStream("little cat.jpg");
            byte[] buffer = new byte[1024];
            int len;
            while ((len = is.read(buffer)) != -1){
                fos.write(buffer,0,len);
            }
        }
        if(is!=null)
        is.close();
        if(fos!=null)
        fos.close();
        JDBCUtils.closeResource(connection,preparedStatement,resultSet);
    }
}

 数据库的批量操作 使用PreparedStatement批量插入操作

update和delete本身具有批量操作的效果,如果不设置过滤条件,那么这个操作默认就是对整体进行。

批量插入操作时Statement和PreparedStatement的对比

 

 

优化批量插入操作

进行批处理

public class Test1  {
    @Test
    public void test() throws Exception{
        long start = System.currentTimeMillis();
        Connection connection = JDBCUtils.getConnection();
        String sql = "insert into goods values (?,?)";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        for (int i = 0;i < 2000;i++){
            preparedStatement.setObject(1,i);
            preparedStatement.setString(2,"good");
            //1.攒sql
            preparedStatement.addBatch();
                //每攒500个执行命令就统一执行一次
            if(i % 500 == 0){
                //2.执行batch
                preparedStatement.executeBatch();
                //3.清空batch
                preparedStatement.clearBatch();
            }

        }
        long end = System.currentTimeMillis();
        System.out.println(end-start);//5345
        //409
        JDBCUtils.closeResource(connection,preparedStatement,null);
    }
}

MySQL默认不支持这种操作,需要更改配置文件信息

 未优化前插入两万条数据要5345毫秒,优化后409毫秒。

进一步优化

public class Test1  {
    @Test
    public void test() throws Exception{
        long start = System.currentTimeMillis();
        Connection connection = JDBCUtils.getConnection();
        String sql = "insert into goods values (?,?)";
        connection.setAutoCommit(false);
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        for (int i = 0;i < 2000;i++){
            preparedStatement.setObject(1,i);
            preparedStatement.setString(2,"good");
            //1.攒sql
            preparedStatement.addBatch();
            if(i % 500 == 0){
                //2.执行batch
                preparedStatement.executeBatch();
                //3.清空batch
                preparedStatement.clearBatch();
            }

        }
        connection.commit();
        long end = System.currentTimeMillis();
        System.out.println(end-start);//5345
        //409
        //443
        JDBCUtils.closeResource(connection,preparedStatement,null);

设置数据不默认提交,先传送,等着传完了统一提交。因为提交的时候也需要耗费时间。

DAO及其实现类

作用:封装了针对于数据表的通用的操作。增删改查这些操作。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/749057.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号