接口

接口:API。 规范。 定义方法签名。

接口和抽象了的意义上的区别。

抽象类是类,抽象类的目的就是让其他类来继承的。 只要继承从意义上来说就要说通 is a。

接口更趋向于功能。希望某一个类有接口指定的功能,就让这个类实现这个接口。

实现

接口属于java的语言的四大类型之一,而且是非常重要的类型。

语法:

 public interface 接口名{
     常量
     抽象方法
     默认方法(JKD1.8开始才有)
 }

实例: 

 public interface Ink {
     //常量
     public static final int WIDTH = 15;
     //在接口中定义一个成员变量,则默认使用 public static final -->常量
     int HEIGHT = 20;
     //方法默认就是public abstract修饰
     public abstract void methodA();
     //默认就是抽象方法
     void methodB();
     //jdk 1.8开始有了默认方法
     default void methodC(){
         System.out.println("默认方法C");
    }
     default void methodD(){
         System.out.println("默认方法D");
    }
 }

接口中的普通的方法都默认是抽象方法。

接口不能被实例化(直接创建接口对象)。

接口中所有的成员变量默认都是常量。(必须有初始值,不可以改变)

接口中可以有默认方法(1.8)。默认方法可以有方法体。默认方法都是使用default修饰。

接口的实现

我们可以使用任何一类实现一个接口。当然这个类也可以是抽象类。

如果一个类实现了一个接口,就默认继承了这个接口的默认方法。

如果这个类不是抽象了,则这个类必须实现接口中所有的抽象方法。

如果这个类是抽象类,则可以选择实现接口中的 抽象方法。

非抽象类实现接口:

 public class ColorInk implements Ink {
     @Override
     public void methodA() {
         System.out.println("实现的方法methodA");
    }
 
     @Override
     public void methodB() {
         System.out.println("实现的方法methodB");
    }
 }

抽象类实现接口:

 public abstract class OtherInk implements Ink{
     public void methodA() {
         System.out.println("选择实现方法methodA");
    }
 }

接口是多现实的:一个类可以实现多个接口。

1.8之前的接口中是没有默认方法的,所有的方法都是抽象方法,那么一个类实现类两个接口,两个接口中有同样的方法,是不影响的。

案例:

 public interface InterB {
     void methodA();
 }
 public interface InterA {
     void methodA();
 }
 public class InterImpl implements InterA,InterB {
     @Override
     public void methodA() {
         System.out.println("实现的方法methodA");
    }
 }

但是1.8开始有了默认方法,看看效果:

 public interface InterB {
     void methodA();
      default void methodB(){
         System.out.println("InterB - methodB");
    }
 }
 public interface InterA {
     void methodA();
      default void methodB(){
         System.out.println("InterA - methodB");
    }
 }

要求自己实现一个methodB方法:

 public class InterImpl implements InterA,InterB {

     @Override
     public void methodA() {
         System.out.println("实现的方法methodA");
    }
 
     @Override
     public void methodB() {
         System.out.println("InterImpl - methodB");
    }

 }

接口之间的继承: 接口是可以多继承的

接口可以继承接口。

一个接口可以继承多个其他接口。

多继承,如果只有抽象方法,不会产生冲突

 public interface InterA {
     void methodA();
 }
 public interface InterB {
     void methodA();
 }
 public interface InterC extends InterA,InterB {
     void methodA();
 }

如果两个父接口有同名的默认方法:

接口的使用

接口看上去就是比抽象类更抽象一个类。

接口的作用和抽象类类似,但是抽象更趋向于类。而接口更趋向于功能。

使用上和抽象类几乎相同。

抽象类都是 申明抽象类对象, 创建子类实例。

接口也一样。

实现类的类型转向接口也是向上转型,自动转换。 接口类型转向实现类也是向下转型,需要强制类型转换。

    public static void main(String[] args) {
        //申明接口类型,创建实现类对象
        Ink ink = new ColorInk();
        ink.methodA();//实现的方法methodA
        ink.methodB();//实现的方法methodB
        ink.methodC();//默认方法C
        ink.methodD();//默认方法D
    }

tips:如果在实现类中扩展方法

接口的多态:

public interface USB {
    void turnOn();
    void turnOff();
}
public class Mouse implements USB {
    @Override
    public void turnOn() {
        System.out.println("鼠标开");
    }

    @Override
    public void turnOff() {
        System.out.println("鼠标关");
    }
}
public class Mic implements USB {
    @Override
    public void turnOn() {
        System.out.println("音响开");
    }

    @Override
    public void turnOff() {
        System.out.println("音响关");
    }
}
public class Computer {
    public void openUSB(USB usb){
        usb.turnOn();
    }

    public void stopUSB(USB usb){
        usb.turnOff();
    }
}
public class Test {
    public static void main(String[] args) {
        Computer computer = new Computer();
        computer.openUSB(new Mouse());
        computer.openUSB(new Mic());
    }
}

实现类重写默认方法:

public class BlackInk implements Ink{
    @Override
    public void methodA() {
        System.out.println("黑白墨盒-methodA");
    }
    @Override
    public void methodB() {
        System.out.println("黑白墨盒-methodB");
    }
    //重写默认方法
    @Override
    public void methodC() {
        System.out.println("黑白墨盒-methodC");
    }
    @Override
    public void methodD() {
        System.out.println("黑白墨盒-methodD");
    }
}

测试:

        Ink ink = new BlackInk();
        ink.methodA();
        ink.methodB();
        ink.methodC();
        ink.methodD();

案例测试:

public class Test {
    public static void main(String[] args) {
        InterB inter = new InterImpl();
        inter.methodA();//被子类实现
        inter.methodB();//接口中的默认方法被重写的
        //实现类对象可以直接赋值给任意一个父接口。
        InterA ia = new InterImpl();
        InterB ib = new InterImpl();
    }
}

补充:接口中可以定义静态方法。

package com.qidian.demo5;

public interface InterA {
    public static void methodA(){
        System.out.println("接口中的静态方法");
    }
}
public class Test {
    public static void main(String[] args) {
        //接口中的静态方法
        InterA.methodA();
    }
}

JDBC

JDBC是一套接口。java程序要连接数据库,就要有对应的程序访问对应的数据。但是数据库各不相同,java没有办法开发一套程序对应所有的关系型数据库。

我们访问数据无非就四个操作:增删改查。 --> 增删改,查

定义接口:

public List<Data> queryAll(tableName);
public int insert(.....);
只有接口,不做实现....

由数据库开发商实现这些接口。开发商所实现的这一套代码就是JDBC驱动。

驱动? 不同接口之间的数据(信号)转换程序。

JDBC的驱动:java接口和关系型数据库之间的信号转换程序。 不同的数据库就需要不同的转换程序。

java连接数据库的工作原理

 

对于程序员来说,只要关心java提供的操作数据的接口即可。

JDBC常用接口

   DriverManager:这个类管理数据库驱动程序的列表,查看加载的驱动是否符合JAVA Driver API的规范。

   Connection:与数据库中的所有的通信是通过唯一的连接对象。 一个连接。

   Statement:把创建的SQL对象,转而存储到数据库当中。 发送sql。

   ResultSet:它是一个迭代器,用于检索查询数据。 查询的结构。

 

 JDBC的基本操作步骤

[0]准备,准备JDBC的驱动包。

驱动包是数据库开发商提供的,在java库中是没有的,需要我们下载,并且加入到我们的classpath中。

下载驱动:

https://mvnrepository.com/search

搜索mysql,根据自己的版本需求点开:进入详情页面之后,点击下载jar文件:

创建一个java项目,将驱动加入项目的classpath中:

说明: 所谓classpath就是JVM在启动的时候,加载类的路径。 之所以要将jar文件加入到classpath中,就是希望JVM在找驱动的时候能找到。

具体操作:

在项目的根目录下创建一个lib文件夹,将驱动的jar文件拷贝到lib中。

开始将jar文件加入到项目的classpath中:

找到你的驱动jar文件。

添加完成。

 

 

 产生一个连接

public class Demo1 {
    public static void main(String[] args) throws ClassNotFoundException, SQLException {
        // 步骤1 : 加载驱动
        // 参数是驱动类的列名,不同的数据库驱动类名不同。
        // MySQL5.x和MySQL8.x驱动类名不同。
        Class.forName("com.mysql.jdbc.Driver");
        // 步骤2: 获取连接
        // 参数1 : url
        // 参数2: 连接数据库的用户名
        // 参数3: 连接数据库的密码
        Connection con = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/books","root","root123");
        System.out.println(con);
    }
}
获取连接中的url说明: url->统一资源定位器
jdbc:mysql://127.0.0.1:3306/books
jdbc:mysql: 这个是固定的前缀,不同的数据库前缀不同
3306 : MySQL数据库默认的服务监听端口。
127.0.0.1 : 是mysql所在的服务器的地址。如果是本机,可以使用localhost。
/books : 要连接的数据库的名字。

[1]加载驱动。

[2]获取连接。

[3]创建statement。

[4]执行sql语句。

[5]处理执行结果。

[6]关闭连接。

完整案例

public class Demo2 {
    private static final String DRIVER_CLASS="com.mysql.jdbc.Driver";
    private static final String URL = "jdbc:mysql://127.0.0.1:3306/books";
    private static final String USERNAME = "root";
    private static final String PASSWORD = "root123";
    public static void main(String[] args) {
        // 将需要的对象申明在try语句块以外
        Connection con = null;
        Statement st = null;
        ResultSet rs = null;
        try {
            //[1]加载驱动。
            Class.forName(DRIVER_CLASS);
            //[2]获取连接。
            con = DriverManager.getConnection(URL,USERNAME,PASSWORD);
            //[3]创建statement。
            st = con.createStatement();
            //[4]执行sql语句。
            // 准备一个字符串,存储sql语句
            String sql = "select * from g_book";
            rs = st.executeQuery(sql); // 这里的rs对象就是我们查询的一张虚拟表
            //[5]处理执行结果。
            while(rs.next()){// 将结果集的数据逐行遍历
                String isbn = rs.getString("isbn");
                String title = rs.getString("title");
                float cost = rs.getFloat("cost");
                int categoryId = rs.getInt("category_Id");
                System.out.println("isbn:"+isbn +",title:"+title+",cost:"+cost+",categoryId:"+categoryId);
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            try {
                // [6]关闭连接。
                if(rs!=null) {
                    rs.close();
                }
                if(st!=null) {
                    st.close();
                }
                if(con!=null) {
                    con.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

说明:

//        [1]加载驱动。
        Class.forName("com.mysql.jdbc.Driver");

这里使用反射机制将驱动相关的类加载到内存中。

不同的数据库会有不同的驱动的类名。MySQL8的驱动类名不一样。“com.mysql.cj.jdbc.Driver”

//        [2]获取连接。
        Connection con = DriverManager.getConnection("jdbc:mysql://localhost:3306/books","root","root123");

获取连接使用的是java提供的一个类DriverManager,DriverManager中有多个静态方法可以获取一个有效的连接。我们这里使用的方法需要传入三个参数。 三个参数分别是:

url : 连接数据库的地址。

前缀:jdcb:mysql://

服务器地址: localhost 当然也可以写IP地址。 127.0.0.1,

端口:3306。

数据库的名称: /books

username: 连接数据库的用户名。

password: 连接数据库的密码。

//        [3]创建statement。
        Statement st = con.createStatement();

通过Connection对象可以直接创建Statement。

Statement提供了大量的API用来执行的sql语句。

//        [4]执行sql语句。
        ResultSet rs = st.executeQuery("select * from g_book");

我们这里执行的查询操作,索引使用api: executeQuery(sql) 。查询执行结束之后会得到ResultSet对象。

如果是增删改,则使用API: executeUpdate(sql) 。返回数据库受影响的行数。(int)

//        [5]处理执行结果。
        while(rs.next()){
            System.out.print(rs.getString("isbn")+"-");
            System.out.print(rs.getString("title")+"-");
            System.out.print(rs.getString("cost")+"-");
            System.out.print(rs.getString("price")+"\n");
        }

只要查询操作才有这一步。

我们可以简单的认为ResultSet就是一个虚拟表(迭代器),可以迭代处理查询返回的结果。

try {
                // [6]关闭连接。
                if(rs!=null) {
                    rs.close();
                }
                if(st!=null) {
                    st.close();
                }
                if(con!=null) {
                    con.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }

关闭连接。正常情况下 ResultSet,Statement,Connection都是需要关闭的。

JDBC完成添加操作

案例:通过JDBC给boys表添加一条数据

public class Demo2_Insert {
    // 连接数据库需要的常量
    private static final String DRIVER_CLASS="com.mysql.jdbc.Driver";
    private static final String URL = "jdbc:mysql://127.0.0.1:3306/student";
    private static final String USERNAME = "root";
    private static final String PASSWORD = "root123";
    public static void main(String[] args) {
        // 将需要的对象申明在try语句块以外
        Connection con = null;
        Statement st = null;
        // ResultSet rs = null; 增删改都是没有查询结果,的所以不需要ResultSet
        try{
            // 加载驱动
            Class.forName(DRIVER_CLASS);
            // 获取链接
            con = DriverManager.getConnection(URL,USERNAME,PASSWORD);
            // 获取Statement对象
            st = con.createStatement();// 在内部创建了一个Statement接口实现类的对象,并且返回了这个对象。
            // 准备sql语句
            String sql = "insert into boys(id,boyname,usercp) values(6,'鸣人',50000)";
            // 执行sql  增删改操作全部使用executeUpdate
            int result = st.executeUpdate(sql);
            // 这里result就是数据库受影响的行数
            System.out.println(result);
        }catch (Exception e){
            e.printStackTrace();// 将异常信息输出到控制台
        }finally {
            try{
                if(st!=null){
                    st.close();
                }
                if(con !=null ){
                    con.close();
                }
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }
}

说明几个问题:

  • st使用什么方法执行sql语句。

在JDBC中,对数据库的操作只有两种。分别是修改和查询。所谓修改就是对数据库的数据产生了影响,比新增数据,修改数据,删除数据。 查询是对数据本身不产生任何影响的。

所以在执行增删改操作的时候统一使用 executeUpdate(sql) 返回的结果是数据库受影响的行数。

查询操作统一使用 executeQuery(sql) 返回的结果是查询的结果集(ResultSet对象)

JDBC完成修改操作

修改操作和添加操作的流程完全一致,仅仅修改修改sql语句即可。

public class Demo2_update {
    // 连接数据库需要的常量
    private static final String DRIVER_CLASS="com.mysql.jdbc.Driver";
    private static final String URL = "jdbc:mysql://127.0.0.1:3306/student";
    private static final String USERNAME = "root";
    private static final String PASSWORD = "root123";
    public static void main(String[] args) {
        // 将需要的对象申明在try语句块以外
        Connection con = null;
        Statement st = null;
        // ResultSet rs = null; 增删改都是没有查询结果,的所以不需要ResultSet
        try{
            // 加载驱动
            Class.forName(DRIVER_CLASS);
            // 获取链接
            con = DriverManager.getConnection(URL,USERNAME,PASSWORD);
            // 获取Statement对象
            st = con.createStatement();// 在内部创建了一个Statement接口实现类的对象,并且返回了这个对象。
            // 准备sql语句
            String sql = "update boys set boyname = 'mingren' where id = 6";
            // 执行sql  增删改操作全部使用executeUpdate
            int result = st.executeUpdate(sql);
            // 这里result就是数据库受影响的行数
            System.out.println(result);
        }catch (Exception e){
            e.printStackTrace();// 将异常信息输出到控制台
        }finally {
            try{
                if(st!=null){
                    st.close();
                }
                if(con !=null ){
                    con.close();
                }
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }
}

JDBC完成删除操作

删除操作。。。。 程序就略了。在JDBC中增删改是同一个操作。

所有JDBC只有两个操作: update和query。

所以statement有两个重要的API: executeUpdate和executeQuery

JDBC查询操作-多行数据查询

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
public class Demo2_query {
    // 连接数据库需要的常量
    private static final String DRIVER_CLASS="com.mysql.jdbc.Driver";
    private static final String URL = "jdbc:mysql://127.0.0.1:3306/student";
    private static final String USERNAME = "root";
    private static final String PASSWORD = "root123";
    public static void main(String[] args) {
        // 将需要的对象申明在try语句块以外
        Connection con = null;
        Statement st = null;
        ResultSet rs = null; //增删改都是没有查询结果,的所以不需要ResultSet
        try{
            // 加载驱动
            Class.forName(DRIVER_CLASS);
            // 获取链接
            con = DriverManager.getConnection(URL,USERNAME,PASSWORD);
            // 获取Statement对象
            st = con.createStatement();// 在内部创建了一个Statement接口实现类的对象,并且返回了这个对象。
            // 准备sql语句
            String sql = "select * from beauty";
            // 执行sql  查询操作全部使用executeQuery(sql);
            rs = st.executeQuery(sql);
            // rs就是一个结果集
            while(rs.next()){// 返回true说明这一行剥离成功
                // 取出剥离出来的数据  rs.getXxxx(parm) xxx是对应的数据类型。
                // parm可以是列的索引,索引从1开始计算  这个参数还可以是列名(不区分大小写)
                int id = rs.getInt(1);
                String name = rs.getString("name");// rs.getString(2);
                String sex = rs.getString("sex");
                String birth = rs.getString("borndate");
                String phone = rs.getString("phone");
                int boyfriendId = rs.getInt("boyfriend_id");
                System.out.println("id:"+id+",name:"+name+",sex:"+sex+",birth:"+birth+",phone:"+phone+",boyfriendId:"+boyfriendId);
            }
        }catch (Exception e){
            e.printStackTrace();// 将异常信息输出到控制台
        }finally {
            try{
                if(rs!=null){
                    rs.close();
                }
                if(st!=null){
                    st.close();
                }
                if(con !=null ){
                    con.close();
                }
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }
}

看图理解结果集处理:

我们使用select * from beauty查询一张虚拟表,在ResultSet中:

会有一个指针指向每一行的上面,当我们执行rs.next的时候,指针会向下移动,移动成功就返回true,否则返回false。当到大最后一行之后,再next就返回false。

每移动一次,就剥离一行出来。我们可以通过rs.getXxx()方法获取这一行的所有的列的数据。

JDBC查询操作-单行数据查询

查询一条数据和查询多条数据本身是没有啥区别

public class Demo2_query_one {
    // 连接数据库需要的常量
    private static final String DRIVER_CLASS="com.mysql.jdbc.Driver";
    private static final String URL = "jdbc:mysql://127.0.0.1:3306/student";
    private static final String USERNAME = "root";
    private static final String PASSWORD = "root123";
    public static void main(String[] args) {
        // 将需要的对象申明在try语句块以外
        Connection con = null;
        Statement st = null;
        ResultSet rs = null; //增删改都是没有查询结果,的所以不需要ResultSet
        try{
            // 加载驱动
            Class.forName(DRIVER_CLASS);
            // 获取链接
            con = DriverManager.getConnection(URL,USERNAME,PASSWORD);
            // 获取Statement对象
            st = con.createStatement();// 在内部创建了一个Statement接口实现类的对象,并且返回了这个对象。
            // 准备sql语句
            String sql = "select * from beauty where id = 1";
            // 执行sql  查询操作全部使用executeQuery(sql);
            rs = st.executeQuery(sql);
            // rs就是一个结果集
            if(rs.next()){// 返回true说明这一行剥离成功
                // 取出剥离出来的数据  rs.getXxxx(parm) xxx是对应的数据类型。
                // parm可以是列的索引,索引从1开始计算  这个参数还可以是列名(不区分大小写)
                int id = rs.getInt(1);
                String name = rs.getString("name");// rs.getString(2);
                String sex = rs.getString("sex");
                String birth = rs.getString("borndate");
                String phone = rs.getString("phone");
                int boyfriendId = rs.getInt("boyfriend_id");
                System.out.println("id:"+id+",name:"+name+",sex:"+sex+",birth:"+birth+",phone:"+phone+",boyfriendId:"+boyfriendId);
            }
        }catch (Exception e){
            e.printStackTrace();// 将异常信息输出到控制台
        }finally {
            try{
                if(rs!=null){
                    rs.close();
                }
                if(st!=null){
                    st.close();
                }
                if(con !=null ){
                    con.close();
                }
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }
}

JDBC查询操作-单行单列数据查询

统计有多少美女:

public class Demo2_query_count {
    // 连接数据库需要的常量
    private static final String DRIVER_CLASS="com.mysql.jdbc.Driver";
    private static final String URL = "jdbc:mysql://127.0.0.1:3306/student";
    private static final String USERNAME = "root";
    private static final String PASSWORD = "root123";
    public static void main(String[] args) {
        // 将需要的对象申明在try语句块以外
        Connection con = null;
        Statement st = null;
        ResultSet rs = null; //增删改都是没有查询结果,的所以不需要ResultSet
        try{
            // 加载驱动
            Class.forName(DRIVER_CLASS);
            // 获取链接
            con = DriverManager.getConnection(URL,USERNAME,PASSWORD);
            // 获取Statement对象
            st = con.createStatement();// 在内部创建了一个Statement接口实现类的对象,并且返回了这个对象。
            // 准备sql语句
            String sql = "select count(*) from beauty";
            // 执行sql  查询操作全部使用executeQuery(sql);
            rs = st.executeQuery(sql);
            // rs就是一个结果集
            if(rs.next()){// 返回true说明这一行剥离成功
                int count = rs.getInt(1); // 直接根据索引取出
                System.out.println("总共有"+count+"美女的信息");
            }
        }catch (Exception e){
            e.printStackTrace();// 将异常信息输出到控制台
        }finally {
            try{
                if(rs!=null){
                    rs.close();
                }
                if(st!=null){
                    st.close();
                }
                if(con !=null ){
                    con.close();
                }
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }
}

经过上面的代码浏览,你会发现,有一个固定的框架:

public class Demo2_query_count {
    // 连接数据库需要的常量
    private static final String DRIVER_CLASS="com.mysql.jdbc.Driver";
    private static final String URL = "jdbc:mysql://127.0.0.1:3306/student";
    private static final String USERNAME = "root";
    private static final String PASSWORD = "root123";
    public static void main(String[] args) {
        Connection con = null;
        Statement st = null;
        ResultSet rs = null; //增删改都是没有查询结果,的所以不需要ResultSet
        try{
            Class.forName(DRIVER_CLASS);
            con = DriverManager.getConnection(URL,USERNAME,PASSWORD);
            st = con.createStatement();
            //-------------------------------------
            
            // 准备sql语句
            String sql = "select count(*) from beauty";
            // 执行sql  查询操作全部使用executeQuery(sql);
            //rs = st.executeQuery(sql);
            // rs就是一个结果集
            //if(rs.next()){// 返回true说明这一行剥离成功
            //    int count = rs.getInt(1); // 直接根据索引取出
            //    System.out.println("总共有"+count+"美女的信息");
            //}
            //----------------------------------------
        }catch (Exception e){
            e.printStackTrace();// 将异常信息输出到控制台
        }finally {
            try{
                if(rs!=null){
                    rs.close();
                }
                if(st!=null){
                    st.close();
                }
                if(con !=null ){
                    con.close();
                }
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }
}

MySQL8.0的driver和url的写法:

URL写法:
jdbc:mysql://localhost:3306/数据库名称?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&rewriteBatchedStatements=true
driver写法:
com.mysql.cj.jdbc.Driver

常见的错误:

[1]数据库用户名和密码写错了。

[2]驱动找不到的错误

驱动类名字写错了或者驱动的jar文件没有添加到classpath下。

[3]url写错了

[4]SQL语句的拼写错误

表明写错,或者数据库名写错

sql语句的语法错误:

[5]使用rs.getXxxx(cname)的时候列名拼写错误

乱码问题

我们计算机使用的数据都是二进制,如果要将‘hello’存储到磁盘上,就需要将字符转换为二进制。使用一定的规则来将对应的字符转换为可以存储、传输的形式的方式就是编码。

编码规则有好多种。 ISO8859-1, 中文编码:GBK,GB2312,GB18030。 国际通用编码UTF-8。

乱码是怎么来?

输入的编码和输出的编码不一致导致的。

  • 存储的时候使用的编码和读取的时候使用的编码不一致。

  • 发送的时候使用的编码和接收的时候使用的编码不一致。

现在的问题:数据从APP到数据库就乱码了。

  • 创建数据库的时候,设置数据库的编码为utf-8。

  • 在使用JDBC连接数据数据库的时候,设置连接参数中的编码也为utf-8。

设置方式:在URL后面添加参数。

 jdbc:mysql://127.0.0.1:3306/student?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&rewriteBatchedStatements=true

JDBC的封装

JDBC的使用方式。

DAO:

实现下面的接口:

 public interface BookDAO {
     /** * 根据传入的参数添加一本图书信息到图书表中 *
      * @return 返回数据库受影响的行数
      * */
     public int save(String isbn,String title,float cost,float price,String pid,int categoryId);
     /**
      * 根据传入的参数修改一本图书信息
      * tips:isbn是不能修改的,所以update 是根据isbn修改其他的列的值
      * @return 返回数据库受影响的行数
      * */
     public int update(String isbn,String title,float cost,float price,String pid,int categoryId);
     /**
      * 根据isbn删除一本图书的信息 * @return 返回数据库受影响的行数
      * */
     public int delete(String isbn);
     /** *
      * 查询图书的总条数,返回总条数
      * */
     public int queryTotal();
     /** *
      * 根据isbn查询一本图书的信息,要求返回map,map结构如下 *
      * {isbn:95279527,title:'一本测试图书',cost:25.5,price:35.5,pname:'胡说八道出版 社',cname:'童书'} *
      * tips: 要连接查询的
      * */
     public Map queryByIsbn(String isbn);
     /** *
      * 根据标题查询图书信息. 注意要模糊查询 *
      * sql = "select * from book where title like '%"+title+"%'; *
      * 返回一个list,list中放的都是map,结构如下面的例子 */
     public List<Map> queryByTitle(String title);
 }

具体实现:

 public class BookDAOImpl implements BookDAO {
     // 连接数据库需要的常量
     private final String DRIVER_CLASS="com.mysql.jdbc.Driver";
     private final String URL = "jdbc:mysql://127.0.0.1:3306/books?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&rewriteBatchedStatements=true";
     private final String USERNAME = "root";
     private final String PASSWORD = "root123";
     @Override
     public int save(String isbn, String title, float cost, float price, String pid, int categoryId) {
         // 拼接sql
         String sql = "insert into g_book(Isbn,Title,Cost,Price,Pid,Category_id) " +
                 " values('" + isbn + "','" + title + "'," + cost + "," + price + ",'" + pid + "'," + categoryId + ")";
         int result = 0;
         Connection con = null;
         Statement st = null;
         try {
             // 得到连接
             con = DriverManager.getConnection(URL,USERNAME,PASSWORD);
             // 创建Statement
             st = con.createStatement();
             // 执行sql语句
             result = st.executeUpdate(sql);
        }catch (Exception e){
             e.printStackTrace();
        }finally {
             try{
                 if(st !=null ){
                     st.close();
                }
                 if(con !=null ){
                     con.close();
                }
            }catch (Exception ex){
                 ex.printStackTrace();
            }
        }
         return result;
    }

     @Override
     public int update(String isbn, String title, float cost, float price, String pid, int categoryId) {
         // 拼接sql
         String sql = "update g_book set title = '"+title+"',cost = "+cost+",price= "+price+",pid = '"+pid+"',category_id="+ categoryId +" where isbn = '" + isbn + "'";
         int result = 0;
         Connection con = null;
         Statement st = null;
         try {
             // 得到连接
             con = DriverManager.getConnection(URL,USERNAME,PASSWORD);
             // 创建Statement
             st = con.createStatement();
             // 执行sql语句
             result = st.executeUpdate(sql);
        }catch (Exception e){
             e.printStackTrace();
        }finally {
             try{
                 if(st !=null ){
                     st.close();
                }
                 if(con !=null ){
                     con.close();
                }
            }catch (Exception ex){
                 ex.printStackTrace();
            }
        }
         return result;
    }
 
     @Override
     public int delete(String isbn) {
         // 拼接sql
         String sql = "delete from g_book where isbn ='"+isbn+"'";
         int result = 0;
         Connection con = null;
         Statement st = null;
         try {
             // 得到连接
             con = DriverManager.getConnection(URL,USERNAME,PASSWORD);
             // 创建Statement
             st = con.createStatement();
             // 执行sql语句
             result = st.executeUpdate(sql);
        }catch (Exception e){
             e.printStackTrace();
        }finally {
             try{
                 if(st !=null ){
                     st.close();
                }
                 if(con !=null ){
                     con.close();
                }
            }catch (Exception ex){
                 ex.printStackTrace();
            }
        }
         return result;
    }
 
     @Override
     public int queryTotal() {
         return 0;
    }
 
     @Override
     public Map queryByIsbn(String isbn) {
         return null;
    }
 
     @Override
     public List<Map> queryByTitle(String title) {
         return null;
    }
 }

问题1

在上面的实现过程中,大量重复代码出现,这里可以考虑将这些代码进行封装。

所有的增删改只是修改了sql语句,所以可以将增删改直接封装成一个方法:

 // 执行增删改的sql语句  参数就是要执行的sql
 public int executeUpdateNOParma(String sql){
     int result = 0;
     Connection con = null;
     Statement st = null;
     try {
         // 得到连接
         con = DriverManager.getConnection(URL,USERNAME,PASSWORD);
         // 创建Statement
         st = con.createStatement();
         // 执行sql语句
         result = st.executeUpdate(sql);
    }catch (Exception e){
         e.printStackTrace();
    }finally {
         try{
             if(st !=null ){
                 st.close();
            }
             if(con !=null ){
                 con.close();
            }
        }catch (Exception ex){
             ex.printStackTrace();
        }
    }
     return result;
 }

增删改的操作就可以修改为:

     @Override
     public int save(String isbn, String title, float cost, float price, String pid, int categoryId) {
         // 拼接sql
         String sql = "insert into g_book(Isbn,Title,Cost,Price,Pid,Category_id) " +
                 " values('" + isbn + "','" + title + "'," + cost + "," + price + ",'" + pid + "'," + categoryId + ")";
         // 调用封装的方法执行sql
         return executeUpdateNoParma(sql);
    }
 
     @Override
     public int update(String isbn, String title, float cost, float price, String pid, int categoryId) {
         // 拼接sql
         String sql = "update g_book set title = '"+title+"',cost = "+cost+",price= "+price+",pid = '"+pid+"',category_id="+ categoryId +" where isbn = '" + isbn + "'";
         // 调用封装的方法执行sql
         return executeUpdateNoParma(sql);
    }
 
     @Override
     public int delete(String isbn) {
         // 拼接sql
         String sql = "delete from g_book where isbn ='"+isbn+"'";
         // 调用封装的方法执行sql
         return executeUpdateNoParma(sql);
    }

在自己的方法中,只要拼接好sql,然后调用封装的方法直接执行即可。

为了代码更好的重用,我们可以考虑定义一个BaseDAO,将封装的方法放在BaseDAO中,并且让其他的DAO类继承BaseDAO。

BaseDAO中的代码:

 package com.qidian.demo1;
 
 import java.sql.Connection;
 import java.sql.DriverManager;
 import java.sql.Statement;
 public abstract class BaseDAO {
     // 连接数据库需要的常量
     private final String DRIVER_CLASS="com.mysql.jdbc.Driver";
     private final String URL = "jdbc:mysql://127.0.0.1:3306/books?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&rewriteBatchedStatements=true";
     private final String USERNAME = "root";
     private final String PASSWORD = "root123";
     // 执行增删改的sql语句 参数就是要执行的sql
     public int executeUpdateNoParma(String sql){
         int result = 0;
         Connection con = null;
         Statement st = null;
         try {
             // 得到连接
             con = DriverManager.getConnection(URL,USERNAME,PASSWORD);
             // 创建Statement
             st = con.createStatement();
             // 执行sql语句
             result = st.executeUpdate(sql);
        }catch (Exception e){
             e.printStackTrace();
        }finally {
             try{
                 if(st !=null ){
                     st.close();
                }
                 if(con !=null ){
                     con.close();
                }
            }catch (Exception ex){
                 ex.printStackTrace();
            }
        }
         return result;
    }
 }

DAO类中就不再需要写任何获取连接的操作了,直接拼接sql,调用父类的方法即可。

 public class BookDAOImpl_2 extends BaseDAO implements BookDAO {
     @Override
     public int save(String isbn, String title, float cost, float price, String pid, int categoryId) {
         // 拼接sql
         String sql = "insert into g_book(Isbn,Title,Cost,Price,Pid,Category_id) " +
                 " values('" + isbn + "','" + title + "'," + cost + "," + price + ",'" + pid + "'," + categoryId + ")";
         // 调用封装的方法执行sql
         return executeUpdateNoParma(sql);
    }
 
     @Override
     public int update(String isbn, String title, float cost, float price, String pid, int categoryId) {
         // 拼接sql
         String sql = "update g_book set title = '"+title+"',cost = "+cost+",price= "+price+",pid = '"+pid+"',category_id="+ categoryId +" where isbn = '" + isbn + "'";
         // 调用封装的方法执行sql
         return executeUpdateNoParma(sql);
    }
 
     @Override
     public int delete(String isbn) {
         // 拼接sql
         String sql = "delete from g_book where isbn ='"+isbn+"'";
         // 调用封装的方法执行sql
         return executeUpdateNoParma(sql);
    }
 
     @Override
     public int queryTotal() {
         return 0;
    }
 
     @Override
     public Map queryByIsbn(String isbn) {
         return null;
    }
 
     @Override
     public List<Map> queryByTitle(String title) {
         return null;
    }
 }

tips:这一步的封装是编写了一个BaseDAO,在其中封装了可以执行没有参数的增删改操作的sql语句的方法。

查询操作

查询操作是有返回值的,有ResultSet的,肯定不能使用之前封装的执行增删改操作的方法。

查询操作其实很难封装一个统一的方法。(就不封装)

但是在查询的操作,同样要获取连接,要关闭相关的接口等等。我们可以考虑做一个简单的封装。

在BaseDAO中添加两个方法:

  • 获取一个有效的连接的方法。

  • 可以关闭相关资源的方法。

 // 得到一个有效的链接
 public Connection getCon(){
     Connection con = null;
     try{
         // 加载驱动
         Class.forName(DRIVER_CLASS);
         // 创建连接
         con = DriverManager.getConnection(URL,USERNAME,PASSWORD);
    }catch (Exception e){
         e.printStackTrace();
    }
     return con;
 }
 // 关闭相关的资源
 public void closeAll(ResultSet rs, Statement st, Connection con){
     try{
         if(rs !=null){
             rs.close();
        }
         if(st !=null ){
             st.close();
        }
         if(con !=null ){
             con.close();
        }
    }catch (Exception ex){
         ex.printStackTrace();
    }
 }

继续查询操作的实现:

由于多个方法中都要使用Connection,Statement和ResultSet所以将这些对象申明为成员变量。

public class BookDAOImpl_2 extends BaseDAO implements BookDAO {
    private Connection con;
    private Statement st;
    private ResultSet rs;
    @Override
    public int queryTotal() {
        int total = 0;
        // 拼接sql
        String sql = "select count(*) from g_book";
        try{
            // 获取连接
            con  = getCon();
            // 创建Statement
            st = con.createStatement();
            //  执行sql
            rs = st.executeQuery(sql);
            // 处理查询结果
            if(rs.next()){
                total = rs.getInt(1);
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            // 关闭相关资源
            closeAll(rs,st,con);
        }
        return total;
    }

    @Override
    public String queryByIsbn(String isbn) {
        String result = null;
        String sql = "select * from g_book where isbn = '"+isbn +"'";
        try{
            con = getCon();
            st = con.createStatement();
            rs = st.executeQuery(sql);
            if(rs.next()){
                result = rs.getString("isbn")+":"+rs.getString("title")+":"+rs.getFloat("cost")+":"+rs.getFloat("price");
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            closeAll(rs,st,con);
        }
        return result;
    }

    @Override
    public List<String> queryByTitle(String title) {
        // 要把集合创建出来
        List<String> books = new ArrayList<>();
        String sql = "select * from g_book where title like '%"+title +"%'";
        try{
            con = getCon();
            st = con.createStatement();
            rs = st.executeQuery(sql);
            while(rs.next()){
                String result = rs.getString("isbn")+":"+rs.getString("title")+":"+rs.getFloat("cost")+":"+rs.getFloat("price");
                // 将拼接好的字符串放入集合
                books.add(result);
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            closeAll(rs,st,con);
        }
        return books;
    }

	// 其他的方法就省略
}

实现一个用户登陆

建表 admin

create  table admin(
	admin_id int not null primary key auto_increment,
    admin_name varchar(100) not null unique,
    admin_pass varchar(100) not null
);

添加AdminDAO实现一个登陆的方法:

接口:

public interface AdminDAO {
    // 根据用户名和密码查询
    public boolean queryByNameAndPass(String adminName,String adminPass);
}

实现类:

public class AdminDAOImpl extends BaseDAO implements AdminDAO {
    private Connection con;
    private Statement st;
    private ResultSet rs;
    @Override
    public boolean queryByNameAndPass(String adminName, String adminPass) {
        String sql = "select * from admin where admin_name = '"+adminName+"' and admin_pass = '"+adminPass+"'";
        try{
            con = getCon();
            st = con.createStatement();
            rs = st.executeQuery(sql);
            if(rs.next()){
                // 查询到数据
                return true;
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            // 关闭相关资源
            closeAll(rs,st,con);
        }
        return false;
    }
}

测试登陆方法:

public class AdminLoginTest {
    public static void main(String[] args) {
        AdminDAO adminDAO = new AdminDAOImpl();
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入登录名:");
        String adminName = sc.nextLine();
        System.out.println("请输入登陆密码:");
        String adminPass = sc.nextLine();
        if(adminDAO.queryByNameAndPass(adminName,adminPass)){
            System.out.println("登陆成功");
        }else{
            System.out.println("登陆失败");
        }
    }
}

使用sql注入的方式登陆系统:

PreparedStatement

PreparedStatement接口是Statement的子接口。

我们可以在之前的程序中使用PreparedStatement代替Statement。

Statement是执行sql语句的。(sql语言要先编译,再执行)

PreparedStatement:预编译的Statement。

用法:

添加一个AuthorDAO:

public interface AuthorDAO {
    // 保存一个作者
    public int save(String authorId,String authroName);
}

实现类:

public class AuthorDAOImpl extends BaseDAO implements AuthorDAO {
    private Connection con;
    private PreparedStatement pst; // 预编译的statement
    private ResultSet rs;
    @Override
    public int save(String authorId, String authroName) {
        int  result = 0;
        // 这里的sql中不传入参数,因为这个sql是要预编译的。
        // 但是要提前写好参数的位置,使用"?"占位
        String sql = "insert into g_author(authorId,authorName) values(?,?)";
        try{
            con = getCon();
            // 创建预编译的statement
            // 创建PreparedStatement的时候就需要传入sql(因为要预编译sql)
            pst = con.prepareStatement(sql);
            // 设置参数
            pst.setString(1,authorId);// 将authorId放在第一个?的位置
            pst.setString(2,authroName);// 将authorName放在第2个?的位置
            // 执行sql   不需要传入sql
            result =  pst.executeUpdate();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            // 关闭时要传入pst
			closeAll(rs,pst,con);
        }
        return result;
    }
}

测试:

public class PreparedTest {
    public static void main(String[] args) {
        AuthorDAO authorDAO = new AuthorDAOImpl();
        authorDAO.save("A005","刘健");
    }
}

PreparedStatement可有有效的防止SQL注入。

因为PreparedStatement在编译的时候,参数还没有传入sql。所以参数无法被编译。参数中的sql就无法执行。

public class AdminDAOImpl_1 extends BaseDAO implements AdminDAO {
    private Connection con;
    private PreparedStatement pst;
    private ResultSet rs;
    @Override
    public boolean queryByNameAndPass(String adminName, String adminPass) {
        String sql = "select * from admin where admin_name = ? and admin_pass = ?";
        System.out.println("执行的sql语句:"+sql);
        try{
            con = getCon();
            pst = con.prepareStatement(sql);
            // 设置参数
            pst.setString(1,adminName);
            pst.setString(2,adminPass);
            rs = pst.executeQuery(); // 编译,执行
            if(rs.next()){
                // 查询到数据
                return true;
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            // 关闭相关资源
            closeAll(rs,pst,con);
        }
        return false;
    }
}

面试题:Statement和PreparedStatement有啥区别?

POJO

如果一个类没有直接继承任何其他类,也没有直接实现其他的接口,这种类就称之为POJO。

public interface BookDAO {
    /** * 根据传入的参数添加一本图书信息到图书表中 *
     * @return 返回数据库受影响的行数
     * */
    public int save(String isbn,String title,float cost,float price,String pid,int categoryId);
  
	//.....
}

添加图书的方法,需要传入6个参数,因为图书就有6个属性。如果图书有60个属性呢?

如果一张数据表的属性特别多,在传递参数的时候容易出错。

于是想到一个办法,就是使用一个POJO来封装这张数据表的所有的列:

我们为book表创建一个POJO:

public class Book {
    // 图书编号,对应g_book的isbn
    private String isbn;
    // 图书标题,对应g_book的title
    private String title;
    // 图书成本,对应g_book的cost
    private float cost;
    // 图书售价,对应g_book的price
    private float price;
    // 图书出版社编号,对应g_book的pid
    private String pid;
    // 图书类别编号,对应g_book的categoryId
    private int categoryId;
	// setter和getter省略
}

修改上面的方法:

public interface BookDAO {
    // 保存一本图书
    int save(Book book);
    // 这个接口中还可以有其他的方法:
    // 根据isbn查询一本图书信息
    Book getByIsbn(String isbn);
    // 修改一本图书信息
    int update(Book book);
    // 查询所有的图书
    ArrayList<Book> queryAll();
    // 分页查询
    ArrayList<Book> queryByPage(int page,int size);
    // 查询总条数
    int queryTotal();
}

有了pojo(实体类),一个Book对象,就表示数据库中book表的一条记录。

实现BookDAO中的方法:

public class BookDAOImpl extends BaseDAO implements BookDAO {
    private Connection con;
    private PreparedStatement pst;
    private ResultSet rs;
    @Override
    public int save(Book book) {
        int result = 0;
        String sql = "insert into g_book(Isbn,Title,Cost,Price,Pid,Category_id) values(?,?,?,?,?,?)";
        try{
            con = getCon();
            pst = con.prepareStatement(sql);
            // 设置参数
            pst.setString(1,book.getIsbn());
            pst.setString(2,book.getTitle());
            pst.setFloat(3,book.getCost());
            pst.setFloat(4,book.getPrice());
            pst.setString(5,book.getPid());
            pst.setInt(6,book.getCategoryId());
            //执行\
            result = pst.executeUpdate();
        }catch (Exception e){
            e.printStackTrace();// 将异常输出到控制台。
        }finally {
            closeAll(rs,pst,con);
        }
        return result;
    }
    // 查询一行记录  当个的book对象
    @Override
    public Book getByIsbn(String isbn) {
        // 申明一个对象
        Book book = null;
        String sql = "select * from g_book where isbn = ?";
        try{
            con = getCon();
            pst = con.prepareStatement(sql);
            // 设置参数
            pst.setString(1,isbn);
            // 执行
            rs = pst.executeQuery();
            // 处理结果
            if(rs.next()){
                book = new Book();
                // 从rs中取出数据,并且设置到book的属性中
                book.setIsbn(rs.getString("isbn"));
                book.setTitle(rs.getString("title"));
                book.setCost(rs.getFloat("cost"));
                book.setPrice(rs.getFloat("price"));
                book.setPid(rs.getString("pid"));
                book.setCategoryId(rs.getInt("category_Id"));
            }
        }catch (Exception e){
            e.printStackTrace();// 将异常输出到控制台。
        }finally {
            closeAll(rs,pst,con);
        }
        return book;
    }

    @Override
    public int update(Book book) {
        int result = 0;
        String sql = "update g_book set Title=?,Cost=?,Price=?,Pid=?,Category_id=? where isbn = ?";
        try{
            con = getCon();
            pst = con.prepareStatement(sql);
            // 设置参数
            pst.setString(6,book.getIsbn());
            pst.setString(1,book.getTitle());
            pst.setFloat(2,book.getCost());
            pst.setFloat(3,book.getPrice());
            pst.setString(4,book.getPid());
            pst.setInt(5,book.getCategoryId());
            //执行
            result = pst.executeUpdate();
        }catch (Exception e){
            e.printStackTrace();// 将异常输出到控制台。
        }finally {
            closeAll(rs,pst,con);
        }
        return result;
    }

    @Override
    public ArrayList<Book> queryAll() {
        ArrayList<Book> books = new ArrayList<>();
        String sql = "select * from g_book";
        try{
            con =getCon();
            pst = con.prepareStatement(sql);
            rs = pst.executeQuery();
            // 处理结果
            while(rs.next()){
                // 将一行记录的每个数据取出,封装在一个book对象中。
                Book book = new Book();
                book.setIsbn(rs.getString("isbn"));
                book.setTitle(rs.getString("title"));
                book.setCost(rs.getFloat("cost"));
                book.setPrice(rs.getFloat("price"));
                book.setPid(rs.getString("pid"));
                book.setCategoryId(rs.getInt("category_Id"));
                // 将这个book对象放到集合中
                books.add(book);
            }
        }catch (Exception e){
            e.printStackTrace();// 将异常输出到控制台。
        }finally {
            closeAll(rs,pst,con);
        }
        return books;
    }

    @Override
    public ArrayList<Book> queryByPage(int page, int size) {
        ArrayList<Book> books = new ArrayList<>();
        String sql = "select * from g_book limit ?,?";
        try{
            con =getCon();
            pst = con.prepareStatement(sql);
            // 设置参数
            pst.setInt(1,(page-1)*size);
            pst.setInt(2,size);
            rs = pst.executeQuery();
            // 处理结果
            while(rs.next()){
                // 将一行记录的每个数据取出,封装在一个book对象中。
                Book book = new Book();
                book.setIsbn(rs.getString("isbn"));
                book.setTitle(rs.getString("title"));
                book.setCost(rs.getFloat("cost"));
                book.setPrice(rs.getFloat("price"));
                book.setPid(rs.getString("pid"));
                book.setCategoryId(rs.getInt("category_Id"));
                // 将这个book对象放到集合中
                books.add(book);
            }
        }catch (Exception e){
            e.printStackTrace();// 将异常输出到控制台。
        }finally {
            closeAll(rs,pst,con);
        }
        return books;
    }

    @Override
    public int queryTotal() {
        int total = 0;
        String sql = "select count(*) from g_book";
        try{
            con =getCon();
            pst = con.prepareStatement(sql);
            rs = pst.executeQuery();
            // 处理结果
            if(rs.next()){
              total = rs.getInt(1);
            }
        }catch (Exception e){
            e.printStackTrace();// 将异常输出到控制台。
        }finally {
            closeAll(rs,pst,con);
        }
        return total;
    }
}

测试程序:

public class Test {
    public static void main(String[] args) {
//        queryByIsbnTest();
//        queryAll();
//        update();
        queryByPage();
    }
    public static void queryByPage(){
        BookDAO bookDAO = new BookDAOImpl();
        ArrayList<Book> books = bookDAO.queryByPage(3, 3);
        for (int i = 0; i < books.size(); i++) {
            System.out.println(books.get(i));
        }
    }
    public static void update(){
        BookDAO bookDAO = new BookDAOImpl();
        // 根据ISBN查询一本图书
        Book book = bookDAO.getByIsbn("9787218107752");
        // 修改属性
        book.setTitle("小火车的一年");
        book.setCost(book.getCost()*365);
        // 更新
        int result = bookDAO.update(book);
        System.out.println(result);
    }
    public static void queryAll(){
        BookDAO bookDAO = new BookDAOImpl();
        ArrayList<Book> books = bookDAO.queryAll();
        for (int i = 0; i < books.size(); i++) {
            System.out.println(books.get(i));
        }
    }
    public static void queryByIsbnTest(){
        BookDAO bookDAO = new BookDAOImpl();
        Book book = bookDAO.getByIsbn("987654123");
        System.out.println(book);
    }
    public static void insertTest(){
        Book b = new Book();
        b.setIsbn("987654123");
        b.setTitle("java从入门到生发之路");
        b.setCost(500.0F);
        b.setPrice(5000);
        b.setPid("P001");
        b.setCategoryId(1);

        BookDAO bookDAO = new BookDAOImpl();
        int result = bookDAO.save(b);
        System.out.println(result);
    }
}

事务

事务:是数据库操作的最小工作单元,是作为单个逻辑工作单元执行的一系列操作;这些操作作为一个整体一起向系 统提交,要么都执行、要么都不执行;

事务是一组不可再分割的操作集合(工作逻辑单元); 通俗点说就是为了达到某个目的而做的一系列的操作要么一起成功(事务提交),要么一起失败(事务回滚)

最常见的例子就转账:

小明给如花转账:

开启事务--------------

①从小明的账户扣除1000块

②给如花的账户增加1000块

事务提交-------------

上面例子的任何步骤一旦出现问题,都会导致事务回滚。

从搭讪到结婚就是事务提交。 女方要求男方重新追求她一次就是事务回滚。

[2]事务的四大特性(一原持久隔离)

1、 原子性(Atomicity):事务中所有操作是不可再分割的原子单位。事务中所有操作要么全部执行成功,要么全 部执行失败。

2、 一致性(Consistency):事务执行后,数据库状态与其它业务规则保持一致。如转账业务,无论 事务执行成功与否,参与转账的两个账号余额之和应该是不变的。

3、 隔离性(Isolation):隔离性是指在并发操作 中,不同事务之间应该隔离开来,使每个并发中的事务不会相互干扰。

4、 持久性(Durability):一旦事务提交成 功,事务中所有的数据操作都必须被持久化到数据库中,即使提交事务后,数据库马上崩溃,在数据库重启时,也必须能保证通过某种机制恢复数据。

[3]原生的JDBC事务处理

对数据库的操作,所有的查询操作都是不需要事务处理的。所有的增删改操作都必须在事务中执行。

事务操作:

开启事务 -- > 执行操作 -->提交或者回滚事务

tips:如果不提交事务,所有的JDBC的增删改操作是不会保存到数据库的。

但是我们之前的操作都是成功的,是因为默认情况下,JDBC执行一条sql就会自动提交事务。

try{
    // 开启事务   关闭自动提交
    con.setAutoCommit(false);
    // 持久化操作(增删改查操作)
    // 提交事务
    con.commit();
}catch(){
    //出错了回滚事务
    con.rollback();
}finally{
    con.setAutoCommit(true);
}

测试案例:

准备一张数据表:

create table account(
	name varchar(20),
    balance int
);
insert into account values('tom',1000);
insert into account values('jerry',1000);

测试事务:

添加一个AccountDAO,添加根据账户修改余额的方法

public class AccountDAO extends BaseDAO {
    private Connection con;
    private PreparedStatement pst;
    private ResultSet rs;
    // 修改账户余额,account账户名,money金额 可以是负值
    public void updateMoney(String account,int money){
        String sql = "update account set balance = balance + ? where name = ?";
        try{
            con = getCon();
            // 关闭自动提交事务
            con.setAutoCommit(false);
            pst = con.prepareStatement(sql);
            pst.setInt(1,money);
            pst.setString(2,account);
            pst.executeUpdate();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            closeAll(rs,pst,con);
        }
    }
}

测试:

public class TransactionTest {
    public static void main(String[] args) {
        AccountDAO accountDAO = new AccountDAO();
        int result = accountDAO.updateMoney("tom",-500);
        System.out.println(result);
    }
}

这个测试得到的结果:即使修改成功,数据库的数据并不会发生改变。就是因为没有提交事务。

手动的提交和回滚事务:

使用con.commit提交,使用con.rollback回滚事务。

public class AccountDAO extends BaseDAO {
    private Connection con;
    private PreparedStatement pst;
    private ResultSet rs;
    // 修改账户余额,account账户名,money金额 可以是负值
    public int updateMoney(String account,int money){
        int result = 0;
        String sql = "update account set balance = balance + ? where name = ?";
        try{
            con = getCon();
            // 关闭自动提交事务
            con.setAutoCommit(false);  // 开启一个新的事务
            pst = con.prepareStatement(sql);
            pst.setInt(1,money);
            pst.setString(2,account);
            result = pst.executeUpdate();
            // 提交事务
            con.commit();
        }catch (Exception e){
            e.printStackTrace();
            //  回滚事务
            try {
                con.rollback();
            } catch (SQLException ex) {
                ex.printStackTrace();
            }
        }finally {
            closeAll(rs,pst,con);
        }
        return result;
    }
}

tom给jerry转账100;

第一步给tom减去100,第二步给jerry加上100.

在没有事务的情况下,如果第二步执行出错,第一步执行成功,会导致数据不一致。

添加一个转账的业务:

/**
 * 转账
 * @param fromAccount 转出账户
 * @param toAccount 转入账户
 * @param money 金额
 */
public void transfer(String fromAccount,String toAccount,int money){
    String sql = "update account set balance = balance + ? where name = ?";
    try{
        con = getCon();
        // 默认的事务
        pst = con.prepareStatement(sql);// 编译sql
        // 第一步---------------------
        // 转出金额
        pst.setInt(1,- money);
        pst.setString(2,fromAccount);
        //  执行
        pst.executeUpdate();
        // 第二步---------------------
        // 输入金额
        pst.setInt(1,money);
        pst.setString(2,toAccount);
        // 执行
        pst.executeUpdate();
    }catch (Exception e){
        e.printStackTrace();
    }finally {
        closeAll(rs,pst,con);
    }
}

测试:

public class TransactionTest {
    public static void main(String[] args) {
        AccountDAO accountDAO = new AccountDAO();
        accountDAO.transfer("tom","jerry",100);
    }
}

测试转账成功。

在第一步和第二步之间产生一个异常。

再次测试:

 

tom的钱减少了,但是jerry的钱没增加。

添加事务处理:

/**
 * 转账
 * @param fromAccount 转出账户
 * @param toAccount 转入账户
 * @param money 金额
 */
public void transfer(String fromAccount,String toAccount,int money){
    String sql = "update account set balance = balance + ? where name = ?";
    try{
        con = getCon();
        // 默认的事务
        // 开启事务
        con.setAutoCommit(false);
        pst = con.prepareStatement(sql);// 编译sql
        // 第一步---------------------
        // 转出金额
        pst.setInt(1,- money);
        pst.setString(2,fromAccount);
        //  执行
        pst.executeUpdate();
        // 制造一个异常
        int x = 10 / 0;
        // 第二步---------------------
        // 输入金额
        pst.setInt(1,money);
        pst.setString(2,toAccount);
        // 执行
        pst.executeUpdate();
        // 提交事务
        con.commit();
    }catch (Exception e){
        e.printStackTrace();
        // 回滚事务
        try {
            con.rollback();
        } catch (SQLException ex) {
            ex.printStackTrace();
        }
    }finally {
        closeAll(rs,pst,con);
    }
}

[4]事务的隔离级别

数据库事务的隔离级别有4种,由低到高分别为Read uncommitted 、Read committed 、Repeatable read 、 Serializable 。而且,在事务的并发操作中可能会出现脏读,不可重复读,幻读,事务丢失。

脏读: (读取了未提交的新事务,然后被回滚了)

事务A读取了事务B中尚未提交的数据。如果事务B回滚,则A读取使用了错误的数据

不可重复读: (读取了提交的新事务,指更新操作)

不可重复读是指在对于数据库中的某个数据,一个事务范围内多次查询却返回了不同的数据值,这是由于在查询间 隔,被另一个事务修改并提交了。

幻读:(也是读取了提交的新事务,指增删操作)

在事务A多次读取构成中,事务B对数据进行了新增操作,导致事务A多次读取的数据不一致。

第一类事物丢失:(称为回滚丢失)

对于第一类事物丢失,就是比如A和B同时在执行一个数据,然后B事物已经提交了,然后A事物回滚了,这样B事物 的操作就因A事物回滚而丢失了。

第二类事物丢失:(提交覆盖丢失)

对于第二类事物丢失,也称为覆盖丢失,就是A和B一起执行一个数据,两个同时取到一个数据,然后B事物首先提 交,但是A事物加下来又提交,这样就覆盖了B事物.

隔离级别

Read uncommitted

读未提交,顾名思义,就是一个事务可以读取另一个未提交事务的数据。 会产生脏读。

Read committed (大部分数据库的默认隔离级别)

读提交,顾名思义,就是一个事务要等另一个事务提交后才能读取数据。 会产生不可重复读。

Repeatable read ( 经过测试MySQL中的这个级别可以解决幻读问题)

重复读,就是在开始读取数据(事务开启)时,不再允许修改操作。 可能会产生幻读。

Serializable

Serializable是最高的事务隔离级别,在该级别下,事务串行化顺序执行,可以避免脏读、不可重复读与幻读。但是 这种事务隔离级别效率低下,比较耗数据库性能,一般不使用。

大多数数据库默认的事务隔离级别是Read committed,比如Sql Server , Oracle。

Mysql的默认隔离级别是Repeatable read。

查询MySQL的隔离级别:

select @@tx_Isolation

测试隔离级别

[1]脏读的测试

public class IsolationTest {
    public static void main(String[] args) throws InterruptedException {
        ThreadA ta = new ThreadA();
        ThreadB tb = new ThreadB();
        ta.start();
        Thread.sleep(1000);
        tb.start();
    }
}
class ThreadA extends Thread{
    public void run() {
        Connection con = null;
        Statement st = null;
        ResultSet rs = null;
        try {
            con = DBUtil.getCon();
            con.setAutoCommit(false);//开启事物
            //设置隔离级别
            con.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED);
            String sql = "update account set balance = balance + 500 where name = 'tom'";
            st = con.createStatement();
            st.executeUpdate(sql);
            //等待
            System.out.println("线程A 等待开始");
            Thread.sleep(3000);
            //醒来之后回滚事务
            int x = 10 / 0;
            // 提交事务
            System.out.println("线程A 提交事务");
            con.commit();
        } catch (Exception e) {
            //e.printStackTrace();
            try {
                //回滚事务
                System.out.println("线程A 回滚事务");
                con.rollback();
            } catch (SQLException ex) {
                ex.printStackTrace();
            }
        }finally {
            //关闭连接
            try{
                if(rs!=null)
                    rs.close();
                if(st!=null)
                    st.close();
                if(con!=null)
                    con.close();
            }catch (SQLException e){
                e.printStackTrace();
            }
        }
    }
}
class ThreadB extends Thread{
    public void run() {
        Connection con = null;
        Statement st = null;
        ResultSet rs = null;
        try {
            con = DBUtil.getCon();
            con.setAutoCommit(false);//开启事物
            //设置隔离级别
            con.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED);
            String sql = "select * from account";
            st = con.createStatement();
            rs = st.executeQuery(sql);
            while(rs.next()){
                System.out.println("B线程:"+rs.getString("name")+":"+rs.getInt("balance"));
            };
            // 提交事务
            System.out.println("线程B 提交事务");
            con.commit();
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            //关闭连接
            try{
                if(rs!=null)
                    rs.close();
                if(st!=null)
                    st.close();
                if(con!=null)
                    con.close();
            }catch (SQLException e){
                e.printStackTrace();
            }
        }
    }
}
class DBUtil{
    private static final String DRIVER_CLASS = "com.mysql.jdbc.Driver";
    private static final String USERNAME = "root";
    private static final String PASSWORD = "root123";
    private static final String URL = "jdbc:mysql://localhost:3306/sttest";
    public static Connection getCon(){
        Connection con = null;
        try {
            //加载驱动
            Class.forName(DRIVER_CLASS);
            //获取连接
            con = DriverManager.getConnection(URL,USERNAME,PASSWORD);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return con;
    }
}

[2]不可重复的测试

import java.sql.*;
public class IsolationTest {
    public static void main(String[] args) throws InterruptedException {
        ThreadA ta = new ThreadA();
        ThreadB tb = new ThreadB();
        tb.start();
        Thread.sleep(1000);
        ta.start();
    }
}
class ThreadA extends Thread{
    public void run() {
        Connection con = null;
        Statement st = null;
        ResultSet rs = null;
        try {
            con = DBUtil.getCon();
            con.setAutoCommit(false);//开启事物
            //设置隔离级别
            con.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ);
            String sql = "update account set balance = balance + 500 where name = 'tom'";
            st = con.createStatement();
            st.executeUpdate(sql);
            //等待
            System.out.println("线程A 等待开始");
//            Thread.sleep(3000);
            //醒来之后回滚事务
//            int x = 10 / 0;
            // 提交事务
            System.out.println("线程A 提交事务");
            con.commit();
        } catch (Exception e) {
            //e.printStackTrace();
            try {
                //回滚事务
                System.out.println("线程A 回滚事务");
                con.rollback();
            } catch (SQLException ex) {
                ex.printStackTrace();
            }
        }finally {
            //关闭连接
            try{
                if(rs!=null)
                    rs.close();
                if(st!=null)
                    st.close();
                if(con!=null)
                    con.close();
            }catch (SQLException e){
                e.printStackTrace();
            }
        }
    }
}
class ThreadB extends Thread{
    public void run() {
        Connection con = null;
        Statement st = null;
        ResultSet rs = null;
        try {
            con = DBUtil.getCon();
            con.setAutoCommit(false);//开启事物
            //设置隔离级别
            con.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ);
            String sql = "select * from account";
            st = con.createStatement();
            rs = st.executeQuery(sql);
            System.out.println("B线程第一次读取:");
            while(rs.next()){
                System.out.println("B线程:"+rs.getString("name")+":"+rs.getInt("balance"));
            };
            Thread.sleep(2000);
            sql = "select * from account";
            st = con.createStatement();
            rs = st.executeQuery(sql);
            System.out.println("B线程第二次读取:");
            while(rs.next()){
                System.out.println("B线程:"+rs.getString("name")+":"+rs.getInt("balance"));
            };
            // 提交事务
            System.out.println("线程B 提交事务");
            con.commit();
        } catch (SQLException | InterruptedException e) {
            e.printStackTrace();
        }finally {
            //关闭连接
            try{
                if(rs!=null)
                    rs.close();
                if(st!=null)
                    st.close();
                if(con!=null)
                    con.close();
            }catch (SQLException e){
                e.printStackTrace();
            }
        }
    }
}
class DBUtil{
    private static final String DRIVER_CLASS = "com.mysql.jdbc.Driver";
    private static final String USERNAME = "root";
    private static final String PASSWORD = "root123";
    private static final String URL = "jdbc:mysql://localhost:3306/sttest";
    public static Connection getCon(){
        Connection con = null;
        try {
            //加载驱动
            Class.forName(DRIVER_CLASS);
            //获取连接
            con = DriverManager.getConnection(URL,USERNAME,PASSWORD);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return con;
    }
}

[3]幻读测试

public class IsolationTest {
    public static void main(String[] args) throws InterruptedException {
        ThreadA ta = new ThreadA();
        ThreadB tb = new ThreadB();
        tb.start();
        Thread.sleep(1000);
        ta.start();
    }
}
class ThreadA extends Thread{
    public void run() {
        Connection con = null;
        Statement st = null;
        ResultSet rs = null;
        try {
            con = DBUtil.getCon();
            con.setAutoCommit(false);//开启事物
            //设置隔离级别
            con.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
//            String sql = "update account set balance = balance + 500 where name = 'tom'";
            String sql = "insert into account values('xiaoming',100)";
            st = con.createStatement();
            st.executeUpdate(sql);
            //等待
            System.out.println("线程A 等待开始");
//            Thread.sleep(3000);
            //醒来之后回滚事务
//            int x = 10 / 0;
            // 提交事务
            System.out.println("线程A 提交事务");
            con.commit();
        } catch (Exception e) {
            //e.printStackTrace();
            try {
                //回滚事务
                System.out.println("线程A 回滚事务");
                con.rollback();
            } catch (SQLException ex) {
                ex.printStackTrace();
            }
        }finally {
            //关闭连接
            try{
                if(rs!=null)
                    rs.close();
                if(st!=null)
                    st.close();
                if(con!=null)
                    con.close();
            }catch (SQLException e){
                e.printStackTrace();
            }
        }
    }
}
class ThreadB extends Thread{
    public void run() {
        Connection con = null;
        Statement st = null;
        ResultSet rs = null;
        try {
            con = DBUtil.getCon();
            con.setAutoCommit(false);//开启事物
            //设置隔离级别
            con.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
            String sql = "select count(*)  from account";
            st = con.createStatement();
            rs = st.executeQuery(sql);
            System.out.println("B线程第一次读取:");
            while(rs.next()){
                System.out.println("B线程:"+rs.getInt(1));
            };
            Thread.sleep(2000);
            sql = "select count(*) from account";
            st = con.createStatement();
            rs = st.executeQuery(sql);
            System.out.println("B线程第二次读取:");
            if(rs.next()){
                System.out.println("B线程:"+rs.getInt(1));
            };
            // 提交事务
            System.out.println("线程B 提交事务");
            con.commit();
        } catch (SQLException | InterruptedException e) {
            e.printStackTrace();
        }finally {
            //关闭连接
            try{
                if(rs!=null)
                    rs.close();
                if(st!=null)
                    st.close();
                if(con!=null)
                    con.close();
            }catch (SQLException e){
                e.printStackTrace();
            }
        }
    }
}
class DBUtil{
    private static final String DRIVER_CLASS = "com.mysql.jdbc.Driver";
    private static final String USERNAME = "root";
    private static final String PASSWORD = "root123";
    private static final String URL = "jdbc:mysql://localhost:3306/sttest";
    public static Connection getCon(){
        Connection con = null;
        try {
            //加载驱动
            Class.forName(DRIVER_CLASS);
            //获取连接
            con = DriverManager.getConnection(URL,USERNAME,PASSWORD);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return con;
    }
}