JDBC
JDBC(Java Data Base Connectivity):Java数据库连接,它是一种用于执行SQL语句的Java API,为多种关系数据库提供统一访问。JDBC是Java提供的一种规范,还是一种协议,它提供了连接SQL数据库的接口,由数据库的各大厂商去实现该接口,实现java和数据库之间的连接。
连接数据库的步骤:
//1.加载驱动
Class.forName("com.mysql.jdbc.Driver");
//2.创建数据库连接
Connection conn = DriverMannager.getConnection("jdbc:mysql://host:port/database","user","password");
//3.操作数据库对象,将sql发送给数据库
Statement stat = conn.createStatement();
//4.执行sql语句
stat.execute(sql);//返回的是一个布尔类型的值,操作成功为true,失败为false
//5.释放资源
stat.close();
conn.close();
关于getConnection()中的URL分为几个部分:jdbc:mysql
指定了使用什么数据库,127.0.0.1
是指需要连接的数据库的IP地址,3306
则是数据库的端口号,department
是数据库名称,useUnicode=true&characterEncoding=UTF8
这一部分则是指定字符集,避免后期传入数据到数据库中产生乱码数据
jdbc:mysql://127.0.0.1:3306/department?useUnicode=true&characterEncoding=UTF8
注意: 建立连接对象内部其实包含了Socket对象,是一个远程的连接,比较耗时,因此真正开发时,为了提高效率会建立连接池来管理连接对象
PreparedStatement(预编译数据库操作对象)
Statement | PreparedStatement | |
---|---|---|
可读性 | 差,需要拼接字符串 | 高,可单独设置每个变量,操作比较灵活 |
预编译 | 普通sql无预编译 | 可配置开启预编译,会提高操作性能 |
安全性 | 差,可能被sql注入到拼接字段 | 强,sql提前预编译,传入的参数中的字符串,如果有特殊字符会被转义 |
String sql3 = "insert into department values (0,?,?,?, now())";
PreparedStatement ps = conn.prepareStatement(sql3);
ps.setObject(1, "sales");
ps.setObject(2, "sale products");
java.sql.Date date = new java.sql.Date(System.currentTimeMillis());
SQL注入攻击
在JDBC操作数据库的时候,是使用SQL拼接的方式来进行操作的,基于这一点,一些用户通过输入特殊的字符,强行的注入一些信息,强行操作数据库,这就是SQL注入,比如用户名输入‘or 1 = 1 --
>> 假设我们后台操作有这样的语句
String sql = "select * from user_table where username=' "
+ userName + " ' and password=' "+ password +" '";
>> 当有恶意的人员输入账户密码之后
select * from t_user where username=
''or 1 = 1 -- and password=''
此时我们可以发现username = ' '
什么也没写,1=1
又是恒成立的,并且用--
将密码注释掉,语句永远可以正确的执行,此时我们就可以对数据库做任何操作,比如删库!!
select* from user_table where
username='' ;DROP DATABASE (databaseName) --' and password=''
而采用PreparedStatement预编译方式可以有效地避免注入攻击,原因是当我们使用setObject()等方法对?部分传参是,PreparedStatement的底层对传入的参数进行转义了,那么我们传入的数据将被当作是一个字符串整体结构,因此不会像Statement一样对数据进行拼接,在一定程度上规避了SQL注入
ResultSet(结果集)
ResultSet对象具有指向当前数据的光标,调用next方法可以将光标移到下一行,这和我们集合中使用的Iterator迭代器很类似。其实ResultSet中就相当是一个集合,存储的是我们查询数据库返回的结果集
String sql = "select * from student";
ResultSet rs = stmt.excuteQuery(sql);
Int id = rs.getInt(1);//获取第一行字段的信息(id)
String name = rs.getString(2);//获取第一行字段的信息(name)
Date emtDate = rs.getDate(3);//获取第一行字段的信息(enrollment date)
这么写大家应该发现一个问题,就是如果一张表中有多个字段,那么我们如果需要获取其中一行的数据还需要一个个的去数,才能拿到对应的值,因此,使用下面这种方式能够更清晰的明确你需要的是什么数据,且可以减少不必要的错误(例如数列数的时候数错了)。
Int id = rs.getInt(id);//获取id字段的信息(id)
String name = rs.getString(name);//获取name字段的信息(name)
Date emtDate = rs.getDate(enrollment);//获取enrollment date字段的信息(enrollment date)
批量导入(Add_batch)
public class BatchTest {
public static void main(String[] args) {
Connection conn = null;
Statement stmt = null;
try {
Class.forName("com.mysql.jdbc.Driver");
String url = "jdbc:mysql://127.0.0.1:3306/department?useUnicode = true & characterEncoding = UTF-8";
conn = DriverManager.getConnection(url, "root", "********");
conn.setAutoCommit(false);
stmt = conn.createStatement();
for(int i = 0; i < 20000; i++) {
stmt.addBatch("insert into worker(w_name,w_age,w_hiredate) values ('marco" + i + "',20,now())");//使用批量导入的效率比一次次的导入效率更高
}
int[] list = stmt.executeBatch();
conn.commit();
System.out.println(Arrays.toString(list));
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
} finally {
if(stmt != null) {
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
注意:
1.一般使用Statement来做批量处理,而不使用 PreparedStatement
2. 事务一定要设为手动提交conn.setAutoCommit(false)
现在我们通过以上的内容做一个简单的案例,将Java和Mysql数据库连接起来,并将Mysql数据库中的操作封装成Java中的方法,首先我们创建一个表 worker
CREATE TABLE `worker` (
`w_id` int(10) NOT NULL AUTO_INCREMENT,
`w_age` int(10) NOT NULL,
`w_name` varchar(20) COLLATE utf8mb4_bin NOT NULL,
`w_hiredate` datetime DEFAULT NULL,
PRIMARY KEY (`w_id`)
) ENGINE=InnoDB AUTO_INCREMENT=40004 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
现在我想通过Java来将数据传输到指定的数据库,首先先完成数据库的基本信息配置
通过Properties将数据持久化保存在properties文件中
public class DatabaseConfig {
private static Properties pro = new Properties();
public static void setConfig() {
String url = "jdbc:mysql://127.0.0.1:3306/department?useUnicode=true& characterEncoding = UTF-8";
String user = "root";
String password = "123456";
pro.setProperty("URL", url);
pro.setProperty("USER", user);
pro.setProperty("PASSWORD", password);
try {
FileOutputStream fos = new FileOutputStream("databaseconfig.txt");
pro.store(fos, "URL+USER+PASSWORD");
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("Storing successfully!!");
}
public static void main(String[] args) {
setConfig();//配置数据库基本配置信息
}
}
然后我们来定义一个JDBCUtil工具类,用来提供创建Connection连接对象的方法,和释放资源的方法
public class JDBCUtil {
private static Properties pro = new Properties();
//获取操作数据库对象
public static Connection getConnection() {
try {
Class.forName("com.mysql.jdbc.Driver");
pro.load(new FileInputStream("databaseconfig.txt"));
String url = (String) pro.get("URL");
String user = (String) pro.get("USER");
String password = (String) pro.get("PASSWORD");
Connection conn = DriverManager.getConnection(url,user,password);
return conn;
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
//释放资源
public static void realse(Connection conn, Statement stmt) {
if(conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(stmt != null) {
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
//释放资源方法重载
public static void realse(Connection conn, Statement stmt, ResultSet rs) {
if(rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
realse(conn, stmt);
}
}
最后创建Jdbc类,提供addData、deleteData、updateData的基本方法
public class Jdbc {
public static void main(String[] args) {
new Jdbc().addData(20, "marco");
new Jdbc().deleteData(40001);
new Jdbc().updateData(40003, 20, "Marco");
new Jdbc().searchData();
}
//添加数据到指定数据库
public void addData(int age, String name) {
Connection conn = JDBCUtil.getConnection();
Statement stmt = null;
try {
//方式一:创建Statement操控数据库对象
stmt = conn.createStatement();
String sql = "insert into worker (w_age,w_name,w_hiredate)
values (" + age + ",'" + name + "', now())";
stmt.execute(sql);
//方式二:创建prepareStatement操控数据库对象(更加灵活)
String sql = "insert into worker (w_age,w_name,w_hiredate) values (?,?,?)";
ps = conn.prepareStatement(sql);
ps.setObject(1, age);
ps.setObject(2, name);
ps.setObject(3, new java.sql.Date(System.currentTimeMillis()));
ps.execute();
} catch (SQLException e) {
e.printStackTrace();
}
JDBCUtil.realse(conn, stmt);
}
//删除某行数据库中的内容
public void deleteData(int id) {
Connection conn = JDBCUtil.getConnection();
Statement stmt = null;
try {
stmt = conn.createStatement();
String sql = "delete from worker where w_id = " + id;
stmt.execute(sql);
} catch (SQLException e) {
e.printStackTrace();
}
JDBCUtil.realse(conn, stmt);
}
//更新数据库
public void updateData(int id, int age, String name) {
Connection conn = JDBCUtil.getConnection();
Statement stmt = null;
try {
stmt = conn.createStatement();
String sql = "update worker set w_age = " + age +
",w_name = '" + name + "' where w_id =" + id;
stmt.execute(sql);
} catch (SQLException e) {
e.printStackTrace();
}
JDBCUtil.realse(conn, stmt);
}
//查找所有数据(当然如果有需求也可以另行传参定义新的方法)
public void searchData() {
Connection conn = JDBCUtil.getConnection();
PreparedStatement ps = null;
try {
String sql = "select * from worker";
ps = conn.prepareStatement(sql);
ResultSet rs = ps.executeQuery();
while(rs.next()) {
System.out.println(rs.getObject(1) + "\t" +
rs.getInt(2) + "\t" + rs.getString(3) + "\t" + rs.getDate(4));
}
} catch (SQLException e) {
e.printStackTrace();
}
JDBCUtil.realse(conn, ps, rs);
}
}
通过上述案例,我们将数据库里所需要执行的操作通过Java的方法封装起来,当我们需要对数据库的数据做修改的时候,只需要调用封装好的方法即可,实现了模块之间的数据连接。
但是上述案例中的查询语句只能查询我们固定好的表 worker,但是在实际开发中,我们可能会需要查询这一个数据库中的多个表,并且将表中的数据导出来。
那么我们可以通过将查询的内容进行封装,在Java中创建一个实体类让类中的属性和数据库中的字段对应起来
首先我需要创建一个User类(当然大家也可以自定义类和对应的表),我这边给User设定的属性一共四个,并给这个Javabean类一个全参构造器和相应的get,set方法,以及重写toString方法
private String userName; private String password; private String telePhone; private String realName;
public class ResultMetaTest{
public static <T> List<T> queryData(Class<T> clz, String sql, Object...param) {
Connection conn = JDBCUtil.getConnection();//创建连接对象
try {
PreparedStatement ps = conn.prepareStatement(sql);//创建操作数据库对象
//设置条件判断中的值
for(int i = 0; i < param.length; i++) {
ps.setObject(i+1, param[i]);
}
ResultSet rs = ps.executeQuery();//获取结果集对象
ResultSetMetaData meta = rs.getMetaData();//获取数据集元信息
int column = meta.getColumnCount();//获取所搜寻的字段个数或者列数
List<T> list = new ArrayList<>();//创建用于储存构建出来的对象的容器
while(rs.next()) {
Map<String,Object> map = new HashMap<>();//创建用于储存保存类的属性的map集合
for(int i = 0; i < column; i++) {
String columnName = meta.getColumnLabel(i+1);//得到每一列的或者每一个字段的名称
Object obj = rs.getObject(columnName);//根据字段名称获取每一行对应的字段值
map.put(columnName, obj);
}
if(!map.isEmpty()) {
T obj = clz.newInstance();//通过传入的类的Class对象创建这个类的对象
Set <Entry <String,Object>> entryset = map.entrySet();
for (Entry<String, Object> entry : entryset) {
String atribute = entry.getKey();//提取这个类中的属性名称
Object value = entry.getValue();//提取这个类中属性的值
Field attribute = clz.getDeclaredField(atribute);
attribute.setAccessible(true);//注意要设置权限为开放
attribute.set(obj, value);//通过反射给对象赋值
}
list.add(obj);//将赋值完成的对象放入ArrayList容器中
}
}
JDBCUtil.realse(conn, ps, rs);//释放资源
return list;
} catch (SQLException | InstantiationException | IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
}
return null;
}
public static void main(String[] args) throws Exception {
String sql = "select u_uname as userName,u_pwd as password,"
+ "u_tele as telePhone,u_realname as realName from userinfo where u_id = ?";
List<User> list = queryData(User.class,sql,1);
System.out.println(list);
}
}
输出结果如下 >>>
Marco’s Java 之【JDBC辅助类封装】