JDBC单元测试DAO模式

Posted xfdhh

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JDBC单元测试DAO模式相关的知识,希望对你有一定的参考价值。

JDBC简介

1、什么是JDBC?

  JDBC(Java DataBase Connectivity,java数据库连接)是一种用于执行SQL语句的Java API(工具)。JDBC是Java访问数据库的标准规范。

  规范:在java中的直接体现是接口

  作用:为不同关系型数据库提供统一的访问,由一组用java语言编写的接口和工具类组成,由各大数据库厂商实现对应接口

2、连接数据库时要先加载驱动

  什么是驱动?

  两个设备要进行通信时,需要满足一定通信数据格式,数据格式由设备提供商规定,设备提供商为设备提供驱动软件,通过软件可以与该设备进行通信。

  Java和数据库要想进行链接,必须提前规定一些数据格式,格式由数据库厂商实现。

  mysql连接工具下载地址:https://dev.mysql.com/downloads/connector/j/

3、JDBC是接口,而JDBC驱动才是接口的实现,没有驱动无法完成数据库连接!每个数据库厂商都有自己的驱动,用来连接自己公司的数据库。

JDBC连接详解

1.通过JDBC连接数据库需要五步

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;

public class Demo01 

    public static void main(String[] args) throws Exception 
        //1、加载驱动
        Class.forName("com.mysql.cj.jdbc.Driver");
        //2、创建连接
        Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/db_test?serverTimezone=GMT%2B8", "root", "root");
        //3、编译sql语句 编译器
        Statement statement = conn.createStatement();
        String sql = "select * from test";
        //4、发送sql 并接受结果
        ResultSet eq = statement.executeQuery(sql);
        //根据列名获取数据 迭代器
        while(eq.next()) 
            int sid = eq.getInt(1);
            String name = eq.getString(2);
            int chinese = eq.getInt("chinese");
            int math = eq.getInt("math");
            System.out.println(sid+"\t"+name+"\t"+chinese+"\t"+math);
        
        //5、关闭资源 
        eq.close();
        statement.close();
        conn.close();
    

2、详解每一步

  1、Class.forName("com.mysql.cj.jdbc.Driver"); //这里写下载的连接工具包中的驱动路径

    通过反射,建立驱动的对象

  2、Connection conn = DriverManager.getConnection(url,username,password);  //获取连接

  url="jdbc:mysql://localhost:3306/db_test?serverTimezone=GMT%2B8" 

  协议,有严格书写规范   主协议名:子协议名://主机名:端口号/数据库名  在连接工具到了6.0之后需要设置时区

    serverTimezone是时区,如果设定serverTimezone=UTC,会比中国时间早8个小时,设定中国则为GMT%2B8

  username = "root", password =  "root"  数据库的用户名和密码

  3、获取发送sql语句的对象

  Statement statement = conn.createStatement();

  4、发送sql,执行并接受结果

  executeQuery(String sql),只能执行select语句,如果执行insert into,update,delete from 都会报错,会把SQL语句传递给mysql数据库去执行

  executeUpdate(String sql),执行更新的SQL语句。只能执行insert into,update,delete from,如果执行select语句会报错,会把SQL语句传递给mysql数据库去执行

  execute(String sql)  执行任意语句,执行select 返回true,执行insert into,update,delete from  返回false

  5、释放资源

  eq.close();statement.close();conn.close();

3、JDBC的增删改查

技术图片
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.Scanner;

public class Demo2 

    public static void main(String[] args) throws Exception
        Connection conn=null;
        Scanner sc=new Scanner(System.in);
        PreparedStatement ps =null;
        ResultSet eq =null;
        Class.forName("com.mysql.cj.jdbc.Driver");
        conn = DriverManager.getConnection("jdbc:mysql:///db_701?serverTimezone=GMT%2B8", "root", "ccc");
        login(conn,sc,eq,ps);
        
    
    public static void login(Connection conn,Scanner sc,ResultSet eq,PreparedStatement ps) throws Exception 
        ps = conn.prepareStatement("select username,pwd from users where id=‘1‘");
        eq = ps.executeQuery();
        int flag=1;
        if(eq.next()) 
            do
                System.out.println("请输入用户名:");
                String s1 = sc.nextLine();
                String s2 = eq.getString(1);
                System.out.println(s2);
                if(s1.equals(s2)) 
                    System.out.println("请输入密码:");
                    String pwd1 = sc.nextLine();
                    String pwd2 = eq.getString(2);
                    if(pwd1.equals(pwd2)) 
                        System.out.println("登录成功,欢迎使用");
                        flag=0;
                    else 
                        System.out.println("用户名密码错误 ,请重新输入");
                        continue;
                    
                else 
                    System.out.println("用户名不存在,请重新输入");
                    continue;
                
            while(flag==1);
            test(conn,sc,eq,ps);
        
    
    public static void test(Connection conn,Scanner sc,ResultSet eq,PreparedStatement ps) throws Exception
        System.out.println("请选择功能:1、添加  2、删除  3、修改  4、查询  0、退出");
        int num =Integer.parseInt(sc.nextLine());
        switch(num)
            case 1:
                add(conn,sc,eq,ps);
                break;
            case 2:
                delete(conn,sc,eq,ps);
                break;
            case 3:
                alter(conn,sc,eq,ps);
                break;
            case 4:
                select(conn,sc,eq,ps);
                break;
            case 5 :
                System.out.println("谢谢您的使用,再见");
                after(conn,sc,eq,ps);
                break;
        
    
    public static void add(Connection conn,Scanner sc,ResultSet eq,PreparedStatement ps) throws Exception 
        ps = conn.prepareStatement("insert into users values(null,?,?)");
        System.out.println("请输入用户名:");
        ps.setString(1, sc.nextLine());
        int flag=1;
        while(flag==1) 
            System.out.println("请输入密码 :");
            String pwd1 = sc.nextLine();
            System.out.println("确认密码:");
            String pwd2 = sc.nextLine();
            if(pwd1.equals(pwd2)) 
                ps.setString(2, pwd2);
                flag=0;
            else 
                System.out.println("两次密码输入不一致,请重新输入密码 ");
                continue;
            
        
        int count=ps.executeUpdate();
        if(count>0) 
            System.out.println("添加成功");
        else 
            System.out.println("添加失败");
        
        test2(conn,sc,eq,ps);    
    
    public static void delete(Connection conn,Scanner sc,ResultSet eq,PreparedStatement ps) throws Exception
        ps = conn.prepareStatement("delete from users where username=? and pwd=?");
        int flag=1;
        while(flag==1) 
            System.out.println("请输入想要删除的用户名:");
            String s = sc.nextLine();
            ps.setString(1, s);
            System.out.println("请输入此用户的密码 :");
            String pwd1 = sc.nextLine();
            ps.setString(2, pwd1);
            int count=ps.executeUpdate();
            if(count>0) 
                System.out.println("删除成功");
                flag=0;
            else 
                System.out.println("用户名密码错误,删除失败  1.继续删除  0.返回 ");
                String s1 = sc.nextLine();
                if(s1.equals("1")) 
                    continue;
                else 
                    flag=0;
                    System.out.println("正在返回上一级");
                
            
        
        test2(conn,sc,eq,ps);    
    
    public static void alter(Connection conn,Scanner sc,ResultSet eq,PreparedStatement ps) throws Exception 
        ps = conn.prepareStatement("update users set pwd=? where username=? and pwd=?");
        int flag=1;
        int flag1=1;
        while(flag==1) 
            System.out.println("请输入想要修改密码的用户名:");
            String s1 = sc.nextLine();
            ps.setString(2, s1);
            System.out.println("请输入原密码 ");
            String pwd = sc.nextLine();
            ps.setString(3, pwd);
            
            while(flag1==1) 
                System.out.println("请输入修改的密码 :");
                String pwd1 = sc.nextLine();
                System.out.println("确认密码:");
                String pwd2 = sc.nextLine();
                if(pwd1.equals(pwd2)) 
                    ps.setString(1, pwd2);
                    flag1=0;
                else 
                    System.out.println("两次密码输入不一致,请重新输入 ");
                    continue;
                
            
            int count=ps.executeUpdate();
            if(count>0) 
                System.out.println("修改成功");
                flag=0;
            else 
                System.out.println("修改失败");
                System.out.println("用户名密码错误,修改失败  1.继续修改  0.返回 ");
                String s3 = sc.nextLine();
                if(s3.equals("1")) 
                    continue;
                else 
                    flag=0;
                    System.out.println("正在返回上一级");
                
                
            
        
        test2(conn,sc,eq,ps);
    
    public static void select(Connection conn,Scanner sc,ResultSet eq,PreparedStatement ps) throws Exception 
        System.out.println("1.查询全部  2.指定用户名查询");
        String s = sc.nextLine();
        if(s.equals("2")) 
            ps = conn.prepareStatement("select * from users where username=?");
            System.out.println("请输入要查询的用户名:");
            String s1 = sc.nextLine();
            ps.setString(1, s1);
            eq = ps.executeQuery();
        else if(s.equals("1")) 
            ps = conn.prepareStatement("select * from users");
            eq = ps.executeQuery();    
        
        if(eq.next()) 
            do
                String username = eq.getString(2);
                String pwd = eq.getString(3);
                System.out.println(username+"\t"+pwd);
            while(eq.next()) ;
        else 
            System.out.println("查询失败");
        
        test2(conn,sc,eq,ps);
    
    public static void test2(Connection conn,Scanner sc,ResultSet eq,PreparedStatement ps) throws Exception
        System.out.println("1.继续  0.退出");
        String s3 = sc.nextLine();
        if(s3.equals("1")) 
            test(conn,sc,eq,ps);
        else 
            System.out.println("谢谢您的使用,再见");
            after(conn,sc,eq,ps);
        
    
    public static void after(Connection conn,Scanner sc,ResultSet eq,PreparedStatement ps) throws Exception
        if(conn!=null) 
            conn.close();
        
        if(ps!=null) 
            ps.close();
        
        if(sc!=null) 
            sc.close();
        
        if(eq !=null) 
            eq.close();
        
    
JDBC增删改查练习之用户管理系统mini

4、工具类的抽取

  将重复的加载驱动和获取连接和关闭资源封装到工具类,方便使用

技术图片
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

public class JDBCUtils 
    public static final String driver = "com.mysql.cj.jdbc.Driver";
    public static final String url = "jdbc:mysql://localhost:3306/db_test?serverTimezone=GMT%2B8";
    public static final String user = "root";
    public static final String password="root";
    static 
        try 
            Class.forName(driver);
         catch (ClassNotFoundException e) 
            e.printStackTrace();
        
        
    
    public static Connection getConn() 
        try 
            return DriverManager.getConnection(url,user,password);
         catch (SQLException e) 
            e.printStackTrace();
            return null;
        
    
    public static void closeAll(ResultSet rs,Statement st,Connection conn) 
        if(rs!=null) 
            try 
                rs.close();
             catch (SQLException e) 
                e.printStackTrace();
            
        
        if(st!=null) 
            try 
                st.close();
             catch (SQLException e) 
                e.printStackTrace();
            
        
        if(conn!=null) 
            try 
                conn.close();
             catch (SQLException e) 
                e.printStackTrace();
            
        
    
工具类

SQL注入问题

1、在上面的JDBC使用中,存在着一个安全问题,那就是SQL的注入问题

  这个问题主要发生在编译对象身上,因为statement对象每次都是将字符串拼接完毕之后才发送给数据库执行,因此就产生了安全问题。

  eg:用户登录  假设用户名密码必须是root 和root才能登录获得查看结果

  sql语句为  String sql = "select * from users where username=‘"+username+"‘ and password=‘"+password+"‘";

  username  和 password需要用户输入

  注入问题如下:

  username = a‘ or ‘a‘=‘a;

  password = a‘ or ‘a‘=‘a;

  此时sql语句变为 select * from users where username = ‘a‘ or ‘a‘=‘a‘ and password=‘a‘ or ‘a‘=‘a‘

  虽然用户名密码均不正确,但依然可以登录成功进行查看,这就是SQL注入问题

2、那么该如何解决呢?

  使用预编译对象 preparedStatement

  PreparedStatement与Statement的区别:

    前者对sql语句进行了预编译

    PreparedStatement将语句先送回数据库

    使语句的逻辑已经确定为select * from users where username=? and password=?

    此时用户如果输入同样的内容,却无法改变原有的逻辑,会产生语法错误,所以避免了注入问题

  且预编译对象在使用同一条语句传不同内容时,也只需编译一次,大大提高了效率

3、PreparedStatement的预编译是数据库进行的,编译后的函数key是缓存在PreparedStatement中的,编译后的函数是缓存在数据库服务器中的。预编译前有检查sql语句语法是否正确的操作。只有数据库服务器支持预编译功能时,JDBC驱动才能够使用数据库的预编译功能,否则会报错。

  jdbc:mysql://localhost:3306/db?useServerPrepStmts=true  可以设置数据库开启预编译

4、使用不同的PreparedStatement对象来执行相同的SQL语句时,还是会出现编译两次的现象,这是因为驱动没有缓存编译后的函数key,导致二次编译。如果希望缓存编译后函数的key,那么就要设置cachePrepStmts参数为true

  jdbc:mysql://localhost:3306/db?useServerPrepStmts=true&cachePrepStmts=true

5、PreparedStatement的预编译还有注意的问题,在数据库端存储的函数和在PreparedStatement中存储的key值,都是建立在数据库连接的基础上的,如果当前数据库连接断开了,数据库端的函数会清空,建立在连接上的PreparedStatement里面的函数key也会被清空,各个连接之间的预编译都是互相独立的。

6、PreparedStatement的使用

setInt(int index,int value)

为?占位符,赋予int值

setString(int index,String value)

为?占位符,赋予String值

setObject(int index,Object value)

 

executeQuery()

执行查询的SQL语句。

只能执行select语句,

如果执行insert into,update,delete from 都会报错

会把SQL语句传递给mysql数据库去执行

 

返回结果:ResultSet对象---一张二维表格

 

executeUpdate()

执行更新的SQL语句。

只能执行insert into,update,delete from

如果执行select语句会报错

会把SQL语句传递给mysql数据库去执行

 

返回结果:int   SQL语句执行后更新了几行数据

 

上面的代码练习中采用的就是PreparedStatement。

单元测试

1、导包 右击项目-->prooerties-->java build path -->libraries-->add library-->JUint-->next finish

2、给想要测试的方法加上注解(在上方加上 @Test)

3、可以在其他方法上加@Before 这个方法会在测试方法之前执行  用来加载资源

   @After 这个方法会在测试方法之后执行   用来关闭资源

DAO模式

Database  Access  Object

把对数据库进行的JDBC操作(增、删、改、查) 都放在一个类中,用不同的方法分别来完成增、删、改、查。

一张数据库表做一个实体类,数据库的列是类的属性,数据库的一行数据是类的一个对象

 

以上是关于JDBC单元测试DAO模式的主要内容,如果未能解决你的问题,请参考以下文章

单元测试 DAO

没有内存数据库的单元测试 dao 层

使用 H2 数据库对 DAO 层进行单元测试

DAO的单元测试

DAO(又名存储库)是不是应该进行单元测试?

如何组织或分类单元测试 DAO 搜索方法