目录
操作前的准备
在使用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