JDBC-操作数据库的增删改查及SQL注入说明

得意时要看淡,失意时要看开。不论得意失意,切莫大意;不论成功失败,切莫止步。志得意满时,需要的是淡然,给自己留一条退路;失意落魄时,需要的是泰然,给自己觅一条出路JDBC-操作数据库的增删改查及SQL注入说明,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

目录

操作前的准备

数据库的增删改

 数据库增添数据操作演示(insert)

增删改通用操作(insert,update,delete)

数据库的查询

查询”一张表的一条记录”

查询”多张表的一条记录”(通用方法)

查询:”多张表的多条记录”(通用方法)

SQL注入问题说明和解决

SQL注入问题

SQL注入的解决

Statement和PrepareStatement区别


操作前的准备

在使用Java操作数据库时,应该先导入对应mysql的jar包

获取数据库连接的方法和关闭流的方法进行封装(手动封装)

1.将获取数据库连接封装方法,在增删改查操作时,调用工具类,就可以获取连接。

2.将关闭资源的操作也封装为方法,调用就可以关闭资源。

 封装代码如下

public class JDBCUtils {

    public static Connection getConnection() throws Exception {

        //1.获取类加载器,获取文件流3
        //类加载器加载的资源,会在内存缓存一段时间后自动销毁,则不需要手动关闭资源。
        ClassLoader classLoader = ClassLoader.getSystemClassLoader();
        //获取配置文件流(properties文件自定义)
        InputStream is = classLoader.getResourceAsStream("connectionData.properties");
        //2.Properties加载流
        Properties properties = new Properties();
        properties.load(is);
        //3.获取信息
        String url = properties.getProperty("url");
        String user = properties.getProperty("user");
        String password = properties.getProperty("password");
        String driver = properties.getProperty("driver");
        //4.加载驱动
        Class.forName(driver);
        //5.连接数据库
        Connection connection = DriverManager.getConnection(url, user, password);

        return connection;
    }

    /**
     *关闭资源 两个参数(针对增删改)
     */
    public static void closeResouce(Connection conn , Statement state){
        try {
            if (state != null)
                state.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        try {
            if (conn != null)
                conn.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    /**
     *关闭资源 三个参数(针对查询)
     */
    public static void closeResouce(Connection conn , Statement state, ResultSet rs){
        try {
            if (state != null)
                state.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        try {
            if (conn != null)
                conn.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        try {
            if (rs != null)
                rs.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

数据库的增删改

因为增删改的操作基本一致,都没有结果集的返回,在此归为一起说明。

在其中,因为Statement存在因拼串而导致SQL注入问题,所以我们在此直接使用prepareStatement,Statement的SQL注入问题,篇末会说明。

 数据库增添数据操作演示(insert)

//获取数据库连接(使用了开始封装的方法)
Connection conn = JDBCUtils.getConnection();
//设置sql语句和获取PreparedStatement对象
String sql = "insert into customers(`name`,email,birth)values(?,?,?)";
PreparedStatement ps = conn.prepareStatement(sql);
//填充占位符
ps.setString(1,"小明");
ps.setString(2,"xiaoming@163.com");
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Date util_date = sdf.parse("2022-9-17");
//转换为java.sql.Date---因为导包为java.util.sql
java.sql.Date sql_date = new java.sql.Date(util_date.getTime());
ps.setDate(3,sql_date);
//执行添加操作。
ps.execute();
//关闭资源(使用了开始封装的方法)
JDBCUtils.closeResouce(conn,ps);

说明:在其中添加日期时,使用了格式化日期以及将util.Date转为sql.Date,因为数据库需要sql.Data类型的日期。转换为sql.Date需要构造时传入util.Date毫秒值即可(getTime())。

增删改通用操作(insert,update,delete)

因为增删改只有sql语句,和填充占位符不同,其他一致,因此可以封装为通用方法。

 封装方法如下 

public void upDate(String sql,Object ...args) throws Exception {
    //获取连接
    Connection conn = JDBCUtils.getConnection();
    //预编译sql语句,和创建ps对象
    PreparedStatement ps = conn.prepareStatement(sql);
    //填充占位符
    for (int i = 0; i < args.length; i++) {
        ps.setObject(i + 1,args[i]);
    }
    //执行
    ps.execute();
    //资源关闭
    JDBCUtils.closeResouce(conn,ps);
}

 测试封装的增删改方法  

@Test
public void test2() throws Exception {

//******删除测试******
String sql = "delete from customers where id = ?";
upDate(sql,21);

//******更新测试(改)******
String sql1 = "update `order` set order_name = ? where order_id = ?";
upDate(sql,"DD",2);

}

数据库的查询

查询”一张表的一条记录”

//获取连接
Connection conn = JDBCUtils.getConnection();
//预编译sql语句和填充
String sql = "select id,`name`,email,birth from customers where id = ?";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setObject(1,1);

//获取结果集
ResultSet resultSet = ps.executeQuery();

//判断结果集是否有数据
if (resultSet.next()){
	//获取结果集数据
int id = resultSet.getInt(1);
String name = resultSet.getString(2);
String email = resultSet.getString(3);
Date date = resultSet.getDate(4);

//将数据封装入对象
Customer customer = new Customer(id, name, email, date);
System.out.println(customer);

查询”多张表的一条记录”(通用方法)

多张表:指的是任意表(使用泛型)都可以使用此方法获取一条记录

 public <T>T getInstance(Class<T> clazz,String sql,Object ...args){
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            //获取连接
            conn = JDBCUtils.getConnection();
            //获取prepareStatement对象
            ps = conn.prepareStatement(sql);
            //填充占位符
            for (int i = 0; i < args.length; i++) {
                ps.setObject(i + 1,args[i]);
            }
            //执行查询,获取结果集
            rs = ps.executeQuery();
            //获取返回结果集的元素据
            ResultSetMetaData metaData = rs.getMetaData();
            //获取列数(用于遍历)
            int columnCount = metaData.getColumnCount();

            if (rs.next()){
                //动态创建对象
                T obj =  clazz.newInstance();
                
                //遍历获取结果集字段名和值
                for (int i = 0; i < columnCount; i++) {
                    //获取结果集的值
                    Object value = rs.getObject(i + 1);
                    //获取结果集字段名
                    String columnName = metaData.getColumnLabel(i + 1);

                    //通过反射给动态创建的对象动态赋值
                    Field field = clazz.getDeclaredField(columnName);
                    field.setAccessible(true);
                    field.set(obj,value);
                }
                //返回对象(一条记录)
                return obj;
            }
        //资源关闭操作
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResouce(conn,ps,rs);
        }
        return null;
    }

对如上方法的测试使用 

@Test
public void test1(){
	//测试表1(sql字段名和类属性名不一致,使用了别名)
	String sql = "select order_id orderId,order_name orderName,order_date orderDate from `order` where order_id = ?";
    //使用运行时类.class对象指定表
	Order order = getInstance(Order.class, sql, 1);
	System.out.println(order);

	//测试表2
	String sql1 =  "select name,birth,email from customers where id = ?";
	Customer cust = getInstance(Customer.class, sql1, 19);
	System.out.println(cust);

}

关于查询方法表的字段名类的属性名不一致的问题说明:

1.在查询方法中,如果表的字段名类的属性名不一致,我们就需要在sql语句中设置别名的方式,使得两者一致。

2.但是设置了别名以后,结果集获取字段名时怎么知道我们想要使用别名呢?

在如上方法可以看到,我们直接使用了getColumnLabel()方法。

在此方法前,还有getColumnName()方法,无论怎样他都获取字段名,不获取别名。

3.getColumnLabel方法作用:

当没有别名时,自动使用列名,相当于兼容getColumnName()方法。

查询:”多张表的多条记录”(通用方法)

将查询结果封装到list集合中,作为返回值。

  封装代码如下 

//返回为一个集合
    public <T> List<T> getInstance(Class<T> clazz, String sql, Object ...args) {
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            conn = JDBCUtils.getConnection();

            ps = conn.prepareStatement(sql);
            for (int i = 0; i < args.length; i++) {
                ps.setObject(i + 1, args[i]);
            }

            rs = ps.executeQuery();
            ResultSetMetaData metaData = rs.getMetaData();
            int columnCount = metaData.getColumnCount();
//            创建集合,承装输出的对象(记录)
            List<T> list = new ArrayList<T>();
            //使用了while,只要还有数据就继续获取
            while (rs.next()) {
                //动态创建对象
                T obj = clazz.newInstance();
                
                //通过结果集元素据获取列进行遍历        
                for (int i = 0; i < columnCount; i++) {
                    //获取结果集列值
                    Object value = rs.getObject(i + 1);
                    //获取结果集的列名或别名
                    String columnName = metaData.getColumnLabel(i + 1);

                    //通过反射给动态创建的对象动态赋值
                    Field field = clazz.getDeclaredField(columnName);
                    field.setAccessible(true);
                    field.set(obj, value);
                }
                //集合承装对象(记录)
                list.add(obj);
            }
            return list;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResouce(conn, ps, rs);
        }
        return null;
    }

 如上方法的测试使用 

//多表多记录,返回list
@Test
public void test(){
	//测试表1
	String sql = "select order_id orderId,order_name orderName,order_date orderDate from `order` where order_id < ?";
	List<Order> list = getInstance(Order.class, sql, 4);
	list.forEach(System.out::println);

	//测试表2
	String sql1 =  "select name,birth,email from customers where id < ?";
	List<Customer> list1 = getInstance(Customer.class, sql1, 12);
	System.out.println(list1);

}

SQL注入问题说明和解决

SQL注入问题

请看如下SQL语句:

select * from t_user
where username = 'aa' and password = 'aa' 

此SQL语句是使用用户名和密码查询表中一条记录。

假设某网站的修改密码系统,要求用户输入原始密码,然后根据用户名在和原始密码在数据库中查找,只要找到记录匹配上,就返回true允许修改。

假设,某一用户的用户名为:aa  原始密码为:aa    想要修改密码,提交到后台,后台SQL语句为:select * from t_user where username = ‘aa’ and password = ‘aa’ —返回true

 这样过滤条件结果是为true,密码可以进行修改。但是,此用户的表弟颇懂SQL,表弟在修改表哥密码时,输入用户为: 6’or  输入密码为: =6or’6’=’6 ,则后台SQL语句就为:

select * form t_user where username = '6'or 'and password = '=6 or '6'='6'

 由上代码,由正常的and关系拼接后变成了or关系,然后最后的’6’=’6’又恒等为true,所以最终where过滤结果为true,也可以修改密码! 这就是SQL的拼接字符SQL注入问题


SQL注入的解决

由上看出SQL注入的危险性,如何解决呢?

 实际上,使用Statement会出现SQL注入问题,因为使用Statement,SQL语句采用先填充SQL语句完毕,再编译,就会导致用户可以拼接字符的漏洞。

由篇头的增删改查案例不难看出,我们都在使用PrepareStatement ,用它替换Statement,PrepareStatement是Statement的子接口,因为PrepareStatement采用了预编译的方式,有效的防止了SQL注入问题。

PrepareStatement的SQL格式:

select * from t_user  where id= ?password = ?;

先进性预编译,再填充?占位符。

Statement和PrepareStatement区别

1.Statement通过先输入数据,后编译sql语句,这样一来就有可能改变了sql的条件关系,造成sql注入问题。

2.Preparedstatement使用预编译的写法,通过占位符的方式先编译,使得条件关系成型,后再填充占位符,不会改变sql语句条件关系,则解决了sql注入问题。

3.PrepareStatement的优点:

①可以操作Blob数据(例如文件),通过流。

②可以实现更高效的批量操作:

因为存在预编译,有大量操作时(例如增数据),只需要一次预编译检查,检查成功将进行缓存,后传入数据即可。但是如果是Statement,就要每一次都进行编译检查

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/154563.html

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!