JDBC与连接池
前言
JDBC可以理解为是一套提供给数据库厂商的一套接口规范,而作为程序员的我们在导入了对应厂商提供驱动jar包,注册驱动后,可以用相同的方式(接口方法)去调用不同厂商的数据库。
我们不同持久层框架底层,如mybatis、hibenate底层的基石
一、获取连接的方式
不同数据库的链接方式
jdbc:oracle:thin:@localhost:1521:sid
jdbc:microsoft:sqlserver//localhost:1433;DatabaseName=sid
jdbc:MysqL://localhost:3306/sid
/**
* 获取数据库链接方式1:但是修改时需要代码,所以一般用方式2
*/
public class Demo1 {
public static void main(String[] args) throws Exception {
// 注册驱动,因为是必要步骤,在很多高版本内部已经替我们写好了,现在只需要加载即可
/*Class clazz = Class.forName("com.MysqL.jdbc.Driver");
Driver driver =(Driver)clazz.newInstance();
DriverManager.registerDriver(driver);*/
Class.forName("com.MysqL.jdbc.Driver");
// 获取连接,分别是数据库地址,用户,密码
Connection conn =
DriverManager.getConnection(
"jdbc:MysqL://127.0.0.1:13306/test1?useUnicode=true&characterEncoding=utf8",
"root",
"123456");
System.out.println(conn);//com.MysqL.jdbc.JDBC4Connection@6fd02e5
}
}
/**
* 获取数据库链接方式2:将变动位置抽取成配置文件
*/
public class Demo1 {
public static void main(String[] args) throws Exception {
InputStream is = Demo1.class.getClassLoader().getResourceAsstream("jdbc.properties");
Properties properties = new Properties();
properties.load(is);
String url = properties.getProperty("url");
String username = properties.getProperty("username");
String password = properties.getProperty("password");
String driver = properties.getProperty("driver");
Class.forName(driver);
// 获取连接
Connection conn = DriverManager.getConnection(url,username,password);
System.out.println(conn);
}
}
/**
* MysqL的Driver类,加载时就会触发静态代码块
*/
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
static {
try {
java.sql.DriverManager.registerDriver(new Driver());
} catch (sqlException E) {
throw new RuntimeException("Can't register driver!");
}
}
public Driver() throws sqlException {
// required for Class.forName().newInstance()
}
}
二、操作数据库
1、使用Statement的方式 - 这种方法有sql注入问题,不要使用
public class Demo1 {
static String url = "";
static String username = "";
static String password = "";
static {
try {
InputStream is = Demo1.class.getClassLoader().getResourceAsstream("jdbc.properties");
Properties properties = new Properties();
properties.load(is);
String driver = properties.getProperty("driver");
Class.forName(driver);
url = properties.getProperty("url");
username = properties.getProperty("username");
password = properties.getProperty("password");
} catch (Exception e) {
e.printstacktrace();
}
}
public static void main(String[] args) throws Exception {
Connection conn = DriverManager.getConnection(url,username,password);
Statement statement = conn.createStatement();
// 假设这个就是传进来的参数
String param = " 5 OR 1 = 1";
String sql = "SELECT * FROM users WHERE uid = " + param;
// 5.处理结果集
ResultSet rs = statement.executeQuery(sql);
while (rs.next()) {
String uid = rs.getString("uid");
String name = rs.getString("name");
String pwd = rs.getString("pwd");
System.out.println(uid + name + pwd);
}
// 资源的关闭
rs.close();
statement.close();
conn.close();
}
}
2、使用PreparedStatement方式 - 无sql注入问题,推荐使用
修改1中的main方法,使用预编译的方式执行sql,我们把要成为条件的位置,使用?占位,然后手动set设置值
public static void main(String[] args) throws Exception {
// 假设这个就是传进来的参数
int param = 9;
String sql = "SELECT * FROM USERS WHERE UID = ?";
Connection conn = DriverManager.getConnection(url,username,password);
PreparedStatement preparedStatement = conn.prepareStatement(sql);
preparedStatement.setInt(1, param);
// 5.处理结果集
ResultSet rs = preparedStatement.executeQuery();
while (rs.next()) {
String uid = rs.getString("uid");
String name = rs.getString("name");
String pwd = rs.getString("pwd");
System.out.println(uid + name + pwd);
}
// 资源的关闭
rs.close();
preparedStatement.close();
conn.close();
}
三、元数据
我们一般会将实体类与数据库表字段进行映射,但是如果按照上面案例的写法,就需要自己创建实体类,然后一个个手动从结果集中取值,实体类set设置值。元数据可以让我们获取到数据库表信息,即字段类型,名称,有了它们我们就可以通过反射来映射进我们的实体类中。
public class Demo1 {
static String url = "";
static String username = "";
static String password = "";
static {
try {
InputStream is = Demo1.class.getClassLoader().getResourceAsstream("jdbc.properties");
Properties properties = new Properties();
properties.load(is);
String driver = properties.getProperty("driver");
Class.forName(driver);
url = properties.getProperty("url");
username = properties.getProperty("username");
password = properties.getProperty("password");
} catch (Exception e) {
e.printstacktrace();
}
}
public static void main(String[] args) throws Exception {
// 假设这个就是传进来的参数
int param = 9;
String sql = "SELECT * FROM USERS WHERE UID = ?";
Connection conn = DriverManager.getConnection(url,username,password);
PreparedStatement preparedStatement = conn.prepareStatement(sql);
preparedStatement.setInt(1, param);
ResultSet rs = preparedStatement.executeQuery();
System.out.println("============获取元数据信息=============");
ResultSetMetaData MetaData = rs.getMetaData();
int columnCount = MetaData.getColumnCount();//列数
// 遍历结果集
while (rs.next()) {
// 这个位置也可以通过反射来获取类型与实体类
Users users = new Users();
// 根据表列数决定循环次数,然后将当条数据每个字段一个个取出来
for (int i = 0; i < columnCount; i++) {
Object colValue = rs.getobject(i + 1);
// 获取列的列明(表中的真实名字)
//String columnName = MetaData.getColumnName(i + 1);
// 获取列的别名,没用则用列名,防止表和实体类名称不一致问题
String columnName = MetaData.getColumnLabel(i + 1);
try {
Field field = Users.class.getDeclaredField(columnName);
field.setAccessible(true);
field.set(users, colValue);
}catch (Exception e){
}
}
System.out.println(users);
}
// 资源的关闭
rs.close();
preparedStatement.close();
conn.close();
}
}
class Users{
int uid;
String name;
String pwd;
@Override
public String toString() {
return "Users{" +
"uid=" + uid +
", name='" + name + '\'' +
", pwd='" + pwd + '\'' +
'}';
}
}
四、开启事务与设置隔离级别
对于同时运行的多个事务,当这些事务访问数据库中相同的数据时,如果没有采取必要的隔离机制,就会导致各种井发问题:。
- 脏读:对于两个事务T1, T2, T1读取了已经被T2更新但还没有被提交的字段。之后,若T2回滚,T1读取的内容就是临时且无效的。
- 不可重复读:对于两个事务T1, T2, T1读取了一个字段,然后T2更新了该字段。之后,T1再次读取同一个字段,值就不同了。
- 幻读:对于两个事务T1, T2, T1从一个表中读取了一个字段,然后T2在该表中插入了一些新的行。之后,如果T1再次读取同一个表,就会多出几行。
public static void main(String[] args) throws Exception {
// 假设这个就是传进来的参数
int param = 9;
String sql = "SELECT * FROM USERS WHERE UID = ?";
Connection conn = DriverManager.getConnection(url,username,password);
PreparedStatement ps = null;
ResultSet rs = null;
try {
// 不让连接自动提交
conn.setAutoCommit(false);
// 设置隔离级别为可重复读
conn.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ);
ps = conn.prepareStatement(sql);
rs = ps.executeQuery();
// 处理结果集.....
// ....
// 提交
conn.commit();
}catch (Exception e){
// 异常了回滚操作
conn.rollback();
}finally {
// 正常每个关闭都需要try catch的,这里减少代码行数没写
rs.close();
ps.close();
conn.close();
}
}
5、数据库连接池
为什么要使用连接池?我们看这句代码Connection conn = DriverManager.getConnection(url,username,password); conn.close();用于获取数据库连接的,用完后还要关闭连接。这就好比打电话,拨通-聊天-挂断,数据库作为我们每分每秒都在使用的工具,频繁的连接是会浪费时间与消耗性能的,每次连接都要重新校验你的身份信息等。连接池可以简单理解为一个List集合,我们把连接放入到集合中,当线程访问时给它分发一个,用完在还回来,节省了建立连接所带来的性能消耗。
连接池的用法大同小异,因为JDK里也给了标准接口DataSource,常用的连接池有DBCP、C3PO、Druid
public static void main(String[] args) throws Exception {
InputStream is = Demo1.class.getClassLoader().getResourceAsstream("jdbc.properties");
Properties properties = new Properties();
properties.load(is);
is.close();
// 创建数据源
DataSource createDataSource = DruidDataSourceFactory.createDataSource(properties);
// 获取连接
Connection connection = createDataSource.getConnection();
// 看似是关闭连接,实际上是放回到连接池
connection.close();
}
/**
* 如果不知道配置文件里有哪些参数,可以点进去看源码,能配置的有很多,下图三个就是用户名、密码、连接地址
*/
public static void config(DruidDataSource dataSource, Map<?, ?> properties) throws sqlException {
String value = null;
value = (String) properties.get(PROP_PASSWORD);
if (value != null) {
dataSource.setPassword(value);
}
value = (String) properties.get(PROP_URL);
if (value != null) {
dataSource.setUrl(value);
}
value = (String) properties.get(PROP_USERNAME);
if (value != null) {
dataSource.setUsername(value);
}
// ....还有好多可以配置的
}