美文网首页技术干货
Java JDBC 操作MySQL,Properties,MyS

Java JDBC 操作MySQL,Properties,MyS

作者: 两分与桥 | 来源:发表于2018-08-12 10:02 被阅读33次

    数据库基本操作

    数据库
        RDBMS,relation database management system,关系型数据库管理系统。
            存放的是结构化数据
        
    SQL:Structured Query Language,结构化查询语言
    
    CRUD
        insert into table_name(field_name,...) values(value,...)
        
        select id,... from table_name where id=xxx,...
        
        update table_name set id=xxx,... where id=xxx
        
        delete from test where ...
    

    JDBC介绍

     * JDBC是Java应用程序和数据库之间的通信桥梁,是Java应用程序访问数据库的通道
    
     * JDBC代表Java数据库连接(Java Database Connectivity),它是用于Java编程语言和数据库之间的
     * 数据库无关连接的标准Java API,换句话说:JDBC是用于在Java语言编程中与数据库连接的API。
     * 
     * 1.JDBC标准主要由一组接口组成,其好处就是统一了各种数据库访问方式。
     * 2.JDBC接口的实现类称为数据库驱动,有各个数据库厂商提供,使用JDBC必须导入特定的驱动
    

    JDBC与数据库通信就是进程间的通信,用的就是套接字。

    对于数据库,我用的是phpstudy自带的MySQL数据库,phpstudy的安装十分简单,一直点击下一步就行,数据库账号密码默认都是root

    java JDBC连接数据库的步骤

     * 1.导入JDBC驱动jar
     * 2.注册JDBC驱动
     *      -参数:"驱动程序类名"
     *      -Class.forName("驱动程序类名")
     * 3.获得Connection对象
     *      -需要三个参数:url,username,password
     *      -连接到数据库
     * 4.创建Statement(语句)对象
     *      -conn.getStatement()方法创建对象
     *      -用于执行SQL语句
     *      -execute(ddl) 执行任何SQL,常用执行DDL,DCL
     *      -executeUpdate(dml) 执行DML语句,如:insert,update,delete
     *      -executeQuery(dql) 执行DQL语句,如:select
     * 5.处理SQL执行结果
     *      -execute(ddl) 如果没有异常则执行成功
     *      -executeUpdate 返回数字,表示更新"行"数量,抛出异常则失败
     *      -executeQuery(dql) 返回ResultSet(结果集)对象,代表执行结果
     *          使用for遍历处理,如果查询失败抛出异常!
     * 6.关闭数据连接,
     *      -conn.close();
    

    maven导包,引入MySQL驱动

    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.46</version>
    </dependency>
    
    package com.libai;
    
    import java.sql.Connection;
    import java.sql.DriverManager;
    import java.sql.Statement;
    
    public class Demo1 {
        public static void main(String[] args) {
            try {
                 * 注册驱动程序
                Class.forName("com.mysql.jdbc.Driver");
    
                 * 连接到数据库
                 * getConnection()方法查找并且尝试连接到数据库
                String url = "jdbc:mysql://localhost:3306/test";
                String username = "root";
                String password = "root";
                Connection conn = DriverManager.getConnection(url, username, password);
    
                 * 输出conn引用对象的实际类型
                 * 证明:驱动程序提供了Connection接口的实现类
                System.out.println(conn.getClass());
                
                 * 创建Statement语句对象
                Statement st = conn.createStatement();
                String ddl = "create table rabbit (id INT PRIMARY KEY auto_increment,name varchar(100))";
                String delsql = "drop table rabbit";
                boolean b = st.execute(delsql);
    
                 * 返回结果:true 表示有结果集
                 *          false 没有结果集
                 *          创建失败抛出异常
                 * 如果没有异常,则创建成功!--- 也就是false
    
                System.out.println(b);
                st.close();
                conn.close();
            }catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    
    打印:
    class com.mysql.jdbc.JDBC4Connection
    false
    

    插入数据,使用executeUpdate()

    String sql = "insert into rabbit set id=2,name='frame2'";
    int n = st.executeUpdate(sql);
    System.out.println(n);
    
    打印:
    1   --- 修改了一行,输出正确
    

    select查询数据,使用executeQuery()

    String sql = "select id,name from rabbit";
    ResultSet n = st.executeQuery(sql);
    
    * 处理结果····
    * n.next():移动结果集游标到下一行,默认在第一行之前
    * 检查是否有数据,如果有返回true,否则false
    while(n.next()) {
        * getXXX(列名):返回结果集当前行指定列名的数据
        System.out.print(n.getInt("id"));
        System.out.print(n.getString("name"));
    }
    

    Properties 是Java专门读取配置文件的API

    db.properties文件,建立在与main同级的resources下

    jdbc.driver=com.mysql.jdbc.Driver
    jdbc.url=jdbc:mysql://localhost/test
    jdbc.username=root
    jdbc.password=root
    
    package com.libai;
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.util.Properties;
    
    * Properties 其目的就是为了读取 .properties文件,本身实现了Map接口
    * 内部是散列表,限定了key和value都是String类型。
    * 方法:load(流)将文件读取为散列表
    * getProperty(key)查询value
    
    public class Demo5 {
        public static void main(String[] args) {
            try {
                Properties prop = new Properties();
                System.out.println(prop);
                System.out.println(prop.size());
                System.out.println(prop.isEmpty());
                
                 * 从包中加载资源
                InputStream input = Demo5.class.getClassLoader().getResourceAsStream("db.properties");
    
                 * 将文件内容读取到散列表中
                prop.load(input);
                
                 * 查询数据,读取文件内容
                String driver = prop.getProperty("jdbc.driver");
                System.out.println(driver);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    
    
    修改数据参数后重新写入到文件中
    Properties prop = new Properties();
    prop.load(new FileInputStream(path));
    prop.setProperty("name", "libai");
    prop.setProperty("age", 22 + "");
    prop.store(new FileOutputStream(path), "我的注释");
    
    

    将Properties与JDBC查询数据库结合起来,方便之后修改数据库连接信息(总不能老是去修改java代码吧)

     * Properties读取配置文件,
     * 
     * Properties是java中专门用于读取配置文件的API
     * 管理数据库连接
     * 在软件中数据库连接使用非常频繁,如果每次都创建连接,就会造成代码的大量冗余,
     * 常规的做法是建立数据库连接工具类,封装数据库连接过程,统一数据库连接过程,使用时候就可以简化代码。
     * 
     * 
     * 实现步骤:
     * 1.创建数据库连接参数文件,db.properties
     * 2.创建DbUtils.java封装数据库连接方法
     *      -利用Properties读取配置文件中的数据库连接参数
     *      -创建方法 getConnection封装数据库连接过程
     * 3.使用getConnection方法
    

    下面是封装了Properties读取数据库配置信息的和连接数据库的类

    package com.libai;
    
    import java.io.InputStream;
    import java.sql.Connection;
    import java.sql.DriverManager;
    import java.util.Properties;
    
    public class DbUtils {
    
         * 封装创建数据库连接的过程,简化数据库连接
         * 因为只需要一份,则定义为静态变量
        static String driver;
        static String url;
        static String username;
        static String password;
        static {
            try {
                Properties cfg = new Properties();
                InputStream input = DbUtils.class.getClassLoader().getResourceAsStream("db.properties");
                cfg.load(input);
                driver = cfg.getProperty("jdbc.driver");
                url = cfg.getProperty("jdbc.url");
                username = cfg.getProperty("jdbc.username");
                password = cfg.getProperty("jdbc.password");
                System.out.println(cfg);
    ----上面这句打印{jdbc.password=root, jdbc.username=root, jdbc.url=jdbc:mysql://localhost/test, initSize=5, jdbc.driver=com.mysql.jdbc.Driver, maxActive=10}
            }catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        public static Connection getConnection() {
            try {
                Class.forName(driver);
                Connection conn = DriverManager.getConnection(url,username,password);
                return conn;
            }catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
        
         * 关闭数据库的连接
        public static void close(Connection conn) {
            try {
                if(conn != null) {
                    conn.close();
                }
            }catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    
     * ResultSet代表DQL查询结果,是2维结果,其内部维护了一个读取数据的游标,
     * 默认情况,游标在第一行数据之前,当调用next()方法时候,游标会向下移动,
     * 并将返回结果集中是否包含数据,如果包含数据就返回true,
     * 结果集还提供了很好get方法用于获取结果集游标指向当前行数据。
    

    调用上面的类实现连接数据库

    package com.libai;
    
    import java.sql.Connection;
    import java.sql.ResultSet;
    import java.sql.Statement;
    
    public class Demo4 {
        public static void main(String[] args) {
            Connection conn = null;
            try {
                conn = DbUtils.getConnection();
                Statement st = conn.createStatement();
                String sql = "select * from rabbit";
                ResultSet rs = st.executeQuery(sql);
                while(rs.next()) {
                    System.out.println(rs.getInt("id")+", "+rs.getString("name"));
                }
            }catch (Exception e) {
                e.printStackTrace();
            }finally {
                DbUtils.close(conn);
            }
        }
    }
    
    打印:
    1, libai
    2, ttt
    

    数据库连接池DBCP

    数据库连接池中存放一定数量的数据库连接,需要使用时从其中取,用完了就放回池中,循环使用(数据库连接的连接和关闭都是十分耗费资源的)

    单例模式:singleton
    
    池(集合)化模式:
        使用有限的对象数量服务于大量的客户端请求。
        
    Datasource
        1.内部是连接池
        2.java.sql.Datasource, Connection getConnection();
        3.Connection.close()
    

    使用DBCP

     * 使用DBCP
     * 
     * 1.导入连接池jar
     * 2.创建连接池对象
     * 3.设置数据库必须的连接参数
     * 4.设置可选的连接池管理策略参数
     * 5.从连接池中获得活动的数据库连接
     * 6.使用连接范围数据库
     * 7.使用以后关闭数据库连接,这个不是真的关闭连接,而是将使用过的连接归还到连接池。
     * 
     * 为了便捷的使用连接池,经常将连接池封装为一个连接管理工具类。
    

    maven导入包DBCP

    <dependency>
        <groupId>commons-dbcp</groupId>
        <artifactId>commons-dbcp</artifactId>
        <version>1.4</version>
    </dependency>
    

    一个数据库连接池的简单例子

    package day02;
    
    import java.sql.Connection;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    import java.sql.Statement;
    
    import org.apache.commons.dbcp.BasicDataSource;
    
    public class Demo1 {
        public static void main(String[] args) throws SQLException {
            BasicDataSource ds = new BasicDataSource();
            
            String driver = "com.mysql.jdbc.Driver";
            String url = "jdbc:mysql://localhost/test";
            String username = "root";
            String password = "root";
    
             * 设置必须的参数
            ds.setDriverClassName(driver);
            ds.setUrl(url);
            ds.setUsername(username);
            ds.setPassword(password);
            
             * 设置连接池的管理策略参数
            ds.setInitialSize(2);  --- 一开始就设置池中存放两个连接
            ds.setMaxActive(100);   ---最多可以有100个连接
            
             * 使用连接池中的数据库连接,取不到就堵塞
            Connection conn = ds.getConnection();
            
             * 执行SQL
            Statement st = conn.createStatement();
            String sql = "select * from rabbit";
            ResultSet rs = st.executeQuery(sql);
            while(rs.next()) {
                int id = rs.getInt("id");
                String name = rs.getString("name");
                System.out.println(id+", "+name);
            }
            
             * 归还连接到数据库连接池!!
            conn.close();
        }
    }
    
    打印:
    1, libai
    2, talent
    3, where
    4, HOW
    

    db.properties文件添加两条配置

    jdbc.driver=com.mysql.jdbc.Driver
    jdbc.url=jdbc:mysql://localhost/test
    jdbc.username=root
    jdbc.password=root
    
    # parameter for BasicDataSource,initSize是初始化建立1个连接,最大2个连接
    initSize=1
    maxActive=2
    

    DBUtils.java 连接池模块,直接调用静态方法就行,配置参数依据上面的db.properties文件,下面所有的代码用到的全都是下面这个DBUtils.java 连接池模块。

    package day02;
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.sql.Connection;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    import java.sql.Statement;
    import java.util.Properties;
    
    import org.apache.commons.dbcp.BasicDataSource;
    
     * 连接池版本的数据库连接管理工具类,适合于并发场合
    public class DBUtils {
        private static String driver;
        private static String url;
        private static String username;
        private static String password;
        private static int initSize;
        private static int maxActive;
        
        private static BasicDataSource ds;
        
        static {        
            try {
                Properties cfg = new Properties();
                InputStream input = DBUtils.class.getClassLoader().getResourceAsStream("db.properties");
                cfg.load(input);
                driver = cfg.getProperty("jdbc.driver");
                url = cfg.getProperty("jdbc.url");
                username = cfg.getProperty("jdbc.username");
                password = cfg.getProperty("jdbc.password");
                initSize = Integer.parseInt(cfg.getProperty("initSize"));
                maxActive = Integer.parseInt(cfg.getProperty("maxActive"));
                
                ds = new BasicDataSource();
                
                 * 初始化连接池
                ds.setDriverClassName(driver);
                ds.setUrl(url);
                ds.setUsername(username);
                ds.setPassword(password);
                ds.setInitialSize(initSize);
                ds.setMaxActive(maxActive);
                
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        
        public static Connection getConnection() {
            Connection conn = null;
            try {
                 * getConnection()从连接池中获取重用的连接,如果连接池满了,
                 * 则等待,如果有连接归还,则获取重用的连接
                conn = ds.getConnection();
                return conn;
            } catch (SQLException e) {
                e.printStackTrace();
                throw new RuntimeException(e);
            }
        }
        
         * 将用过的连接归还到连接池
        public static void close(Connection conn) {
            try {
                if(conn != null) {
                    conn.close();
                }
            }catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    

    调用上面的静态方法连接池

    package day02;
    
    import java.sql.Connection;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    import java.sql.Statement;
    
    public class Demo2 {
        public static void main(String[] args) {
            Connection conn = DBUtils.getConnection();
            Statement st;
            try {
                st = conn.createStatement();
                String sql = "select * from rabbit where id>2";
                ResultSet rs= st.executeQuery(sql);
                while(rs.next()) {
                    int id = rs.getInt("id");
                    String name = rs.getString("name");
                    System.out.println(id+", "+name);
                }
                
            } catch (SQLException e) {
                e.printStackTrace();
            }finally {
                DBUtils.close(conn);
            }
        }
    }
    
    打印:
    3, where
    4, HOW
    

    线程测试数据连接池的效果

    package day02;
    
    import java.sql.Connection;
    import java.sql.ResultSet;
    import java.sql.Statement;
    
     * 线程并发测试
    public class Demo3 {
        public static void main(String[] args) {
            for(int i=0; i<5; i++) {
                Thread t = new DemoThread(5000);
                t.start();
            }
        }
    }
    
    class DemoThread extends Thread{
        int wait;
    
        public DemoThread(int wait) {
            this.wait = wait;
        }
    
        public void run() {
            Connection conn = null;
            try {
                 * getConnection方法在连接池中没有连接可以使用的时候,会堵塞等待
                conn = DBUtils.getConnection();
                Statement st = conn.createStatement();
                String sql = "select 'Helllo' as a from dual";
                ResultSet rs = st.executeQuery(sql);
                while(rs.next()) {
                    System.out.println(Thread.currentThread().getName()+" "+rs.getString("a"));
                }
                Thread.sleep(wait);
            }catch (Exception e) {
                e.printStackTrace();
            }finally {
                System.out.println(Thread.currentThread().getName()+" 归还连接");
                DBUtils.close(conn);
            }
        }
    }
    
     * 连接池中最大只有两个连接时,第三个线程获取连接时会等待,
     * 等待到有连接归还为止
    
    ---- 上面的db.preproties文件中最大连接数设置为2了,所以此处两个两个执行
    打印:
    Thread-0 Helllo
    Thread-3 Helllo
    Thread-3 归还连接
    Thread-0 归还连接
    Thread-1 Helllo
    Thread-2 Helllo
    Thread-2 归还连接
    Thread-1 归还连接
    Thread-4 Helllo
    Thread-4 归还连接
    

    使用PreparedStatement提高数据库系统性能,重用执行计划

    SQL语句翻译成执行计划,才能执行
    SQL语句完全一样(大小写和空格都一样)的情况下,会重用执行计划
    使用带参数的SQL语句可以使得重用执行计划,提高数据库性能
    

    用法跟上面的Statement差不多

    package day02;
    
    import java.sql.Connection;
    import java.sql.PreparedStatement;
    
    public class Demo4 {
        public static void main(String[] args) {
            Connection conn = null;
            try {
                conn = DBUtils.getConnection();
    
                 * 带参数的SQL语句
                String sql = "insert into rabbit (id,name) values (?,?)";
                
                 * 将SQL语句发送到数据库,创建执行计划,返回值ps就代表执行计划
                PreparedStatement ps= conn.prepareStatement(sql);
    
                 * 替换[执行计划]中的参数,2个参数,按照序号发送参数
                ps.setInt(1, 5);
                ps.setString(2, "zhizhang");
    
                 * 执行执行计划
                int n1 = ps.executeUpdate();
                System.out.println(n1);
                
                 * 重用
                ps.setInt(1, 6);
                ps.setString(2, "hello");
                int n2 = ps.executeUpdate();
                System.out.println(n2);
            } catch(Exception e) {
                e.printStackTrace();
            } finally {
                DBUtils.close(conn);
            }
        }
    }
    
    打印:  --执行成功,返回修改的行数
    1
    1
    
    
    换成update,也是类似的
    conn = DBUtils.getConnection();
    String sql = "update rabbit set name=? where id=?";
    PreparedStatement ps = conn.prepareStatement(sql);
    ps.setString(1, "demo change1");
    ps.setInt(2, 5);
    int n1 = ps.executeUpdate();
    System.out.println(n1);
    
    ps.setString(1, "demo change2");
    ps.setInt(2, 6);
    int n2 = ps.executeUpdate();
    System.out.println(n2);
    

    note

     * JDBC
     * 数据库连接池
     * 
     * DriverManager管理数据库连接适合单线程情况,而在多线程并发情况下,为了能够重用数据库连接,
     * 同时控制并发连接总数,避免数据库连接超载,一定要使用数据库连接池。
     * 
     * 连接池原理:
     * 数据库连接池的开源实现非常多,DBCP是常用的连接池之一。
    

    只要用到了SQL语句拼接,要思考是否有SQL注入的风险,PreparedStatement可以避免SQL注入的风险,它将单引号等特殊字符转义。

    * PreparedStatement 对象用于执行带参数的预编译执行计划
     * 
     * 关于执行计划:
     * 1.任何SQL执行过程都是先编译“执行计划”,再执行“执行计划”
     * 2.数据库为了优化性能,在SQL相同的时候,会重用执行计划
     *      -执行计划编译较慢
     *      -重用执行计划可以提提高数据库性能
     * 3.数据库只在SQL语句完全一样时候才重用相同的执行计划
     *      -如果SQL语句中有一个字符的更改,会创建不同的执行计划
     *      -SQL中一个空格或者一个大小写的不同也会创建不同的执行计划
     * 
     * PreparedStatement 好处是可以重复使用执行计划,提高DB效率
     * 
     * 使用步骤:
     * 1.将带参数的SQL发送到数据库创建执行计划
     * 2.替换执行计划中的参数
     * 3.执行执行计划,得到结果
     * 
     * PreparedStatement 可以避免SQL注入攻击,它将单引号等特殊字符加上反斜杠转义了
    

    SQL注入例子

    package day02;
    
    import java.sql.Connection;
    import java.sql.PreparedStatement;
    import java.sql.ResultSet;
    import java.sql.Statement;
    import java.util.Scanner;
    
    public class Demo6 {
        public static void main(String[] args) {
            if(login()) {
                System.out.println("登陆成功");
            }else {
                System.out.println("账户名或密码错误");
            }
        }
        
        
        public static boolean login() {
            Scanner scan = new Scanner(System.in);
            System.out.print("请输入用户名:");
            String username = scan.nextLine().trim();
            System.out.print("请输入密码:");
            String password = scan.nextLine().trim();
            
            Connection conn = null;
            int back = 0;
            try {
                conn = DBUtils.getConnection();
                String sql1 = "select count(*) as back from rabbit where username=? and password=?";
                
                --- 第一种方式,PreparedStatement
                PreparedStatement ps = conn.prepareStatement(sql1);
                ps.setString(1, username);      
                ps.setString(2, password);
                System.out.println("自动拼接:"+ps);
                // ResultSet rs = ps.executeQuery();   ---未执行
                
                --- 第一种方式,Statement
                Statement st = conn.createStatement();
                String sql2 = "select count(*) as back from rabbit where username='"+ username +"' and password='"+ password +"'";
                System.out.println("手动拼接:"+sql2);
                ResultSet rs = st.executeQuery(sql2);
    
                rs.next();
                back = rs.getInt("back");
                System.out.println("back = " + back);
            } catch (Exception e) {
                e.printStackTrace();
            }finally {
                DBUtils.close(conn);
            }
            
            if(back >= 1) {
                return true;
            }else {
                return false;
            }
        }
    }
    
    打印;
    请输入用户名:aa
    请输入密码:123' or '1'='1
    自动拼接:com.mysql.jdbc.JDBC42PreparedStatement@3daa422a: select count(*) as back from rabbit where username='aa' and password='123\' or \'1\'=\'1'
    手动拼接:select count(*) as back from rabbit where username='aa' and password='123' or '1'='1'
    back = 6
    登陆成功
    

    ResultSet中存在可滚动的结果集,但从来不用,其性能差

    获取的ResultSet结果集中,存在获取的数据库信息的元数据,可用ResultSetMetaData API调用获取

     * 结果集元数据
     * ResultSetMetaData用于描述查询结果的相关性信息,其中包含列名称,列数量,类数据类型等
    
    package day03;
    
    import java.sql.Connection;
    import java.sql.PreparedStatement;
    import java.sql.ResultSet;
    
    public class Demo1 {
        public static void main(String[] args) {
            Connection conn = null;
            try {
                conn = DBUtils.getConnection();
                String sql = "select * from rabbit where id=1";
                PreparedStatement ps = conn.prepareStatement(sql);
                ResultSet rs = ps.executeQuery();
                --- 调用元数据,具体的信息都可以从接口中取得,具体可以查看java.sql文档
                System.out.println(rs.getMetaData());
    
                while(rs.next()) {
                    String username = rs.getString("username");
                    String password = rs.getString("password");
                    System.out.println("username = "+username+" password = "+password);
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                DBUtils.close(conn);
            }
        }
    }
    
    打印:
    com.mysql.jdbc.ResultSetMetaData@654f0d9c - Field level information: 
        com.mysql.jdbc.Field@6a400542[catalog=test,tableName=rabbit,originalTableName=rabbit,columnName=id,originalColumnName=id,mysqlType=3(FIELD_TYPE_LONG),flags= AUTO_INCREMENT PRIMARY_KEY, charsetIndex=63, charsetName=US-ASCII]
        com.mysql.jdbc.Field@6580cfdd[catalog=test,tableName=rabbit,originalTableName=rabbit,columnName=username,originalColumnName=username,mysqlType=253(FIELD_TYPE_VAR_STRING),flags=, charsetIndex=8, charsetName=WINDOWS-1252]
        com.mysql.jdbc.Field@7e0b85f9[catalog=test,tableName=rabbit,originalTableName=rabbit,columnName=password,originalColumnName=password,mysqlType=253(FIELD_TYPE_VAR_STRING),flags=, charsetIndex=33, charsetName=UTF-8]
    username = libai password = 111
    

    JDBC事务控制

     * JDBC 事务控制,Transaction 一组不可分割的操作。
     * 
     * 数据库提供了事务控制功能,支持ACID特性。
     * JDBC提供了API,方便调用数据库的事务功能。
     * 
     * 相关API:
     *  - Connection.getAutoCommit():获得当前事务的提交方式,默认为true
     *  - Connection.setAutoCommit():设置事务的提交属性,参数是
     *      -true:自动提交; -false:不自动提交(默认自动提交)
     *  - Connection.commit():提交事务
     *  - Connection.rollback():回滚事务
    
    特点:Atomic,原子性,不可分割,整体性。
         Consistent,一致性,数据不被破坏。
         isolate,隔离性,事务之间是独立的,不能被干扰的。
         Durable,持续性,数据被永久保存起来。
    

    模拟转账,不符合条件抛出错误回滚,
    注意:MySQL数据库测试表格的数据引擎要设置为InnoDB,默认的MyISAM不支持事务处理。

    package day03;
    
    import java.sql.Connection;
    import java.sql.PreparedStatement;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    
    public class Demo2 {
        public static void main(String[] args) {
            Connection conn = null;
            try {
                conn = DBUtils.getConnection();
                conn.setAutoCommit(false);
                String sql1 = "update myaccount set balance=balance+? where id=?";
                String sql2 = "select balance from myaccount where id=?";
                PreparedStatement ps = conn.prepareStatement(sql1);
                
                * 一个账户减少 1000
                ps.setInt(1, -1000);
                ps.setInt(2, 1);
                int n1 = ps.executeUpdate();
                System.out.println(n1);
                
                * 一个账户增加 1000
                ps.setInt(1, 1000);
                ps.setInt(2, 2);
                int n2 = ps.executeUpdate();
                System.out.println(n2);
                
                ps.close();
                
                * 检测余额是否大于等于0
                ps = conn.prepareStatement(sql2);
                ps.setInt(1, 1);
                ResultSet rs = ps.executeQuery();
                rs.next();
                int balance = rs.getInt("balance");
                if(balance<0) {
                    throw new RuntimeException("余额不足,转账失败");
                }else {
                    System.out.println("转账成功");
                }
    
                * 延时20s期间,在命令行中查看当前修改的表格时,是看不到本次交易结果的
                * 命令行中修改时,会堵塞到此次修改完毕
                System.out.println("延时中");
                Thread.sleep(20000);
                
                conn.commit();
            } catch (Exception e) {
                e.printStackTrace();
                try {
                    if(conn != null) {
                        conn.rollback();
                    }
                } catch (SQLException e1) {
                    e1.printStackTrace();
                }
            } finally {
                DBUtils.close(conn);
            }
        }
    }
    
    打印:
    1
    1
    转账成功
    延时中
    


    效果图
    mysql事务测试
    JDBC批量SQL处理
    package day03;
    
    import java.sql.Connection;
    import java.sql.Statement;
    import java.util.Arrays;
    
    public class Demo3 {
        public static void main(String[] args) {
            String sql1 = "CREATE TABLE IF NOT EXISTS `account_01` (" + 
                    "  `id` int(11) NOT NULL," + 
                    "  `balance` int(11) NOT NULL," + 
                    "  PRIMARY KEY (`id`)" + 
                    ") ENGINE=InnoDB DEFAULT CHARSET=latin1;";
            String sql2 = "CREATE TABLE IF NOT EXISTS `account_02` (" + 
                    "  `id` int(11) NOT NULL," + 
                    "  `balance` int(11) NOT NULL," + 
                    "  PRIMARY KEY (`id`)" + 
                    ") ENGINE=InnoDB DEFAULT CHARSET=latin1;";
            String sql3 = "CREATE TABLE IF NOT EXISTS `account_03` (" + 
                    "  `id` int(11) NOT NULL," + 
                    "  `balance` int(11) NOT NULL," + 
                    "  PRIMARY KEY (`id`)" + 
                    ") ENGINE=InnoDB DEFAULT CHARSET=latin1;";
            Connection conn = null;
            try {
                conn = DBUtils.getConnection();
                Statement ps = conn.createStatement();
    
                 * 将SQL语句添加到Statement缓存中
                ps.addBatch(sql1);
                ps.addBatch(sql2);
                ps.addBatch(sql3);
    
                 * 执行一批SQL
                int[] n = ps.executeBatch();
                System.out.println(Arrays.toString(n));
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                DBUtils.close(conn);
            }
        }
    }
    
    打印:
    [0, 0, 0]      ---执行成功
    

    Statement批量插入数据,注意,SQL语句中的并不是单引号,而是tab键上面的那个键的符号,不使用(`)也是可以的。

     * 批量执行
     * 批处理:发送到数据库作为一个单元执行的一组更新语句
     * 
     * 批处理降低了应用程序和数据库之间的网络调用,相比单个SQL语句的处理,批处理更为有效
     * 
     * API方法:
     * - addBatch(String sql)
     *      - Statement类的方法,可以将多条SQL语句添加到Statement对象的
     *        SQL语句列表中
     * - addBatch()
     *      - PreparedStatement类的方法,可以将多条预编译的SQL语句添加到
     *        PreparedStatement对象的SQL语句列表中
     * - executeBatch()
     *      - 把Statement对象或PreparedStatement对象语句列表中的所有SQL
     *       语句发送给数据库进行处理
     * - clearBatch()
     *      - 清空当前SQL语句列表
     * 
     * 
     * 防止批量过大出现OutOfMemory错误:
     * 如果PreparedStatement对象中的SQL列表包含过多的待处理SQL语句,可能会
     * 产生OutOfMemory错误,分段处理缓冲列表
    
    package day03;
    
    import java.sql.Connection;
    import java.sql.Statement;
    import java.util.Arrays;
    
    public class Demo3 {
        public static void main(String[] args) {
            String sql1 = "insert into myaccount (`id`,`balance`) values (1,1000)";
            String sql2 = "insert into myaccount (`id`,`balance`) values (2,2000)";
            String sql3 = "insert into myaccount (`id`,`balance`) values (3,3000)";
            Connection conn = null;
            try {
                conn = DBUtils.getConnection();
                Statement ps = conn.createStatement();
    
                 * 将SQL语句添加到Statement缓存中
                ps.addBatch(sql1);
                ps.addBatch(sql2);
                ps.addBatch(sql3);
    
                 * 执行一批SQL
                int[] n = ps.executeBatch();
                System.out.println(Arrays.toString(n));
                
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                DBUtils.close(conn);
            }
        }
    }
    
    打印:
    [1, 1, 1]     ---执行成功
    

    PreparedStatement批量插入数据

    package day03;
    
    import java.sql.Connection;
    import java.sql.PreparedStatement;
    import java.util.Arrays;
    
    public class Demo4 {
        public static void main(String[] args) {
            String sql = "insert into myaccount (id,balance) values(?,?)";
            Connection conn = DBUtils.getConnection();
            try {
                PreparedStatement ps = conn.prepareStatement(sql);
                for(int i=0; i<100; i++) {
    
                     * 替换参数
                    ps.setInt(1, i+100);
                    ps.setInt(2, i*1000);
    
                     * 将参数添加到ps缓冲区
                    ps.addBatch();
    
                     * 清空缓冲区,一般是在数据量特别大的时候避免内存溢出
                    if(i%8 == 0) {
                        ps.executeBatch();
                        ps.clearBatch();
                    }
                }
                int[] n = ps.executeBatch();
                System.out.println(Arrays.toString(n));
                
            } catch (Exception e) {
                e.printStackTrace();    
            } finally {
                DBUtils.close(conn);
            }
        }
    }
    
    打印:
    [1, 1, 1]   --- 每8个一批已经被执行过了
    

    返回自动主键

    JDBC API提供了返回插入数据期间自动生成ID的API
    PreparedStatement ps = conn.prepareStatement(sql,列名列表);
    ResutSet rs = ps.getGeneratedKeys();
    

    向数据库中插入/读取image和长文本

    package test;
    
    import java.io.FileInputStream;
    import java.io.FileOutputStream;
    import java.io.InputStream;
    import java.sql.Connection;
    import java.sql.PreparedStatement;
    import java.sql.ResultSet;
    
    import org.junit.Test;
    import Utils.JDBCUtils;
    
    public class TestCase2 {
         * 向数据库中插入image,长文本
         * 在数据库中image对应的存储类型是longblob,长文本对应的存储类型是longtext
        @Test
        public void test1() throws Exception {
            Connection conn = JDBCUtils.getConnection();
            String sql = "INSERT INTO customers(name,sex,age,photo,memo) VALUES(?,?,?,?,?)";
            PreparedStatement ppst = conn.prepareStatement(sql);
            ppst.setString(1, "hello");
            ppst.setInt(2, 0);
            ppst.setInt(3, 23);
            ppst.setBinaryStream(4, new FileInputStream("E://TestCase//day20//demo.jpg"));
            ppst.setString(5, "aaaaaaaaaaaaaaaaaaaaa");
            
            ppst.executeUpdate();
            
            ppst.close();
            conn.close();
            System.out.println("over");
        }
        
         * 从数据库中读取image和长文本
        @Test
        public void test2() throws Exception {
            Connection conn = JDBCUtils.getConnection();
            String sql = "SELECT * FROM customers WHERE id=?";
            PreparedStatement ppst = conn.prepareStatement(sql);
            ppst.setInt(1, 1);
            
            ResultSet rs = ppst.executeQuery();
            rs.next();
            int id = rs.getInt("id");
            String name = rs.getString("name");
            int sex = rs.getInt("sex");
            int age = rs.getInt("age");
            InputStream photo = rs.getBinaryStream("photo");
            String memo = rs.getString("memo");
            
            System.out.println(id + ", " + name + ", " + age + ", " + sex + "," + memo);
            System.out.println("photo length = " + photo.available());
            FileOutputStream fos = new FileOutputStream("E://TestCase//day24//demo.jpg");
            byte[] buffer = new byte[1024];
            int len = -1;
            while((len = photo.read(buffer)) != -1) {
                fos.write(buffer, 0, len);
            }
            fos.close();
            
            ppst.close();
            conn.close();
            System.out.println("over");
        }
    }
    

    存储过程

    是在服务器中直接调用的,只需要调用时传递必要的参数,就可以直接执行,速度非常快。

    前提是数据库不再需要其他的参数来执行数据库的操作了

    在Java中用CallableStatement来负责调用数据库的存储过程

    存储过程
        store procedure,存放在数据库中的一组SQL,在服务器端执行
        创建存储过程
            mysql>delimiter //  --声明结束符
        
        create procedure sp_biginsert(in num int)
        begin
            start transaction;
            declare i int default 0;
            while i < num do
                insert into customers(name,age,sex) values(concat('tom',i),i,i);
                set i = i + 1;
            end while;
            commit;
        end//
        
        mysql> show procedure status;//可以查看所有的存储过程
    
    创建存储过程,作用是返回customers表的数据量
        mysql> create procedure sp_count(out param int)
            -> begin
            -> select count(*) into param from customers;
            -> end
            -> //
            Query OK, 0 rows affected (0.11 sec)
    
    调用存储过程
        @Test
        public void test3() throws Exception {
            Connection conn = JDBCUtils.getConnection();
            //输出的
            String sql = "{call sp_count(?)}";
            //创建cst对象
            CallableStatement cst = conn.prepareCall(sql);
            //注册输出参数
            cst.registerOutParameter(1, Types.INTEGER);
            //执行存储过程
            cst.execute();
            //取得输出参数
            int count = cst.getInt(1);
            System.out.println(count);
        }
    打印结果:取得该表的数据量
    10000
    

    返回a+b的值

    返回两个数的合
        mysql> create procedure sp_add(in a int, in b int,out c int)
            -> begin
            -> set c:=a+b;
            -> end
            -> //
            Query OK, 0 rows affected (0.00 sec)
    调用该存储过程
        @Test
        public void test4() throws Exception {
            Connection conn = JDBCUtils.getConnection();
            //输出的
            String sql = "{call sp_add(?,?,?)}";
            //创建cst对象
            CallableStatement cst = conn.prepareCall(sql);
            //对于输入参数需要绑定参数值
            cst.setInt(1, 1);
            cst.setInt(2, 5);
            //注册输出参数
            cst.registerOutParameter(3, Types.INTEGER);
            //执行存储过程
            cst.execute();
            //取得输出参数
            int count = cst.getInt(3);
            System.out.println(count);
        }
    打印结果:
    5
    

    返回a-b的值,一个数可以作为输入量,也可以作为输出量

    返回a-b的值,一个数可以作为输入量,也可以作为输出量
        mysql> create procedure sp_subtract(in a int, inout b int)
            -> begin
            -> set b:=a-b;
            -> end
            -> //
            Query OK, 0 rows affected (0.00 sec)
    
    调用该存储过程
        @Test
        public void test5() throws Exception {
            Connection conn = JDBCUtils.getConnection();
            //输出的
            String sql = "{call sp_subtract(?,?)}";
            //创建cst对象
            CallableStatement cst = conn.prepareCall(sql);
            //对于输入参数需要绑定参数值
            cst.setInt(1, 1);
            cst.setInt(2, 5);
            //注册输出参数
            cst.registerOutParameter(2, Types.INTEGER);
            //执行存储过程
            cst.execute();
            //取得输出参数
            int count = cst.getInt(2);
            System.out.println(count);
        }
    打印结果:
    -4
    

    创建一个函数,并在JDBC中调用

    该函数返回a+b的值
        mysql> create function f_add(a int, b int) returns int
            -> return a+b//
        Query OK, 0 rows affected (0.00 sec)
    
    调用该函数
        @Test
        public void test6() throws Exception {
            Connection conn = JDBCUtils.getConnection();
            //输出的
            String sql = "{? = call f_add(?,?)}";
            //创建cst对象
            CallableStatement cst = conn.prepareCall(sql);
            //对于输入参数需要绑定参数值
            cst.setInt(2, 100);
            cst.setInt(3, 600);
            //注册输出参数
            cst.registerOutParameter(1, Types.INTEGER);
            //执行存储过程
            cst.execute();
            //取得输出参数
            int count = cst.getInt(1);
            System.out.println(count);
        }
    打印结果:
    700
    

    相关文章

      网友评论

        本文标题:Java JDBC 操作MySQL,Properties,MyS

        本文链接:https://www.haomeiwen.com/subject/ptfsbftx.html