JDBC
单元测试
常用注解:
- @Test:用于修饰需要执行的方法
- Before:测试方法前执行的方法
- After:测试方法执行后的方法
JDBC
- JDBC是接口,驱动是接口的实现
- 核心类
- DriverManager
- 注册驱动
- 获取连接
- Connection
- Statement
- ResulSet
- DriverManager
开发步骤
- 注册驱动
Class.forName(“com.myssql.jdbc.Driver”);
- 获取连接
Connection conn = DriverManager.getConnection(url);
- url
- 通用:
jdbc:mysql://localhost:3306/database","count","passwd"
- 扩展:
通用url?useUnicode=true&characerEncoding=UTF8
(不是UTF-8)- useUnicode=true:使用Unicode字节集
- characerEncoding=UTF8:Java程序连接数据库的过程中,使用的字节集为UTF-8
- 通用:
- 获得语句执行者
Statement st = conn.createStatement()
Statement createStatement(int resultSetType, int resultSetConcurrency)
- resultSetType的可选值
- ResultSet.TYPE_FORWARD_ONLY:不滚动结果集
- ResultSet.TYPE_SCROLL_INSENSITIVE:滚动结果集,但结果集数据不会再跟随数据库而变化
- ResultSet.TYPE_SCROLL_SENSITIVE:滚动结果集,但结果集数据会跟随数据库而变化
- resultSetConcurrency的可选值
- CONCUR_READ_ONLY:结果集是只读的,不能通过修改结果集而反向影响数据库
- CONCUR_UPDATABLE:结果集是可更新的,对结果集的更新可以反向影响数据库
- resultSetType的可选值
- 执行SQL语句
ResultSet rs = st.executeQuery()
语句 | 说明 |
---|---|
int executeUpdate(String sql) | 执行insert、update、delete |
ResultSet executeQuery(String sql) | select语句 |
bollean execute(String sql) | 执行select返回true,其他语句返回false;如果true,需要使用getResultSet()获取结果;如果false,需要使用getUpdateCount()获取影响行数 |
addBatch(String sql); clearBatch();executeBatch(); | 执行批处理 |
- 处理结果
ResultSet方法
方法 | 说明 |
---|---|
void beforeFirst() | 把光标放到第一行前面 |
void afterLast() | 把光标放到最后一行 |
boolean first() | 把光标放到第一行的位置上 |
boolean last() | 把光标放到最后一行的位置上 |
boolean isBeforeFirst() | 当前光标位置是否在第一行前面 |
boolean isAfterLast() | 当前光标位置是否在最后一行的后面; |
boolean isFirst() | 当前光标位置是否在第一行上; |
boolean isLast() | 当前光标位置是否在最后一行上; |
boolean previous() | 把光标向上挪一行; |
boolean next() | 把光标向下挪一行; |
boolean relative(int row) | 相对位移,当row为正数时,表示向下移动row行,为负数时表示向上移动row行; |
boolean absolute(int row) | 绝对位移,把光标移动到指定的行上; |
int getRow() | 返回当前光标所在行 |
-
上面方法分为两类,一类用来判断游标位置的,另一类是用来移动游标的。如果结果集是不可滚动的,那么只能使用next()方法来移动游标,而beforeFirst()、afterLast()、first()、last()、previous()、relative()方法都不能使用
-
获取列数据
方法 | 说明 |
---|---|
String getString(int columnIndex) | 获取指定列的String类型数据; |
int getInt(int columnIndex) | 获取指定列的int类型数据; |
double getDouble(int columnIndex) | 获取指定列的double类型数据; |
boolean getBoolean(int columnIndex) | 获取指定列的boolean类型数据; |
Object getObject(int columnIndex) | 获取指定列的Object类型的数据 |
String getString(String columnName) | 获取名称为columnName的列的String数据 |
int getInt(String columnName) | 获取名称为columnName的列的int数据 |
double getDouble(String columnName) | 获取名称为columnName的列的double数据 |
boolean getBoolean(String columnName) | 获取名称为columnName的列的boolean数据 |
Object getObject(String columnName) | 获取名称为columnName的列的Object数据 |
PreparedStatement
-
sql攻击
在需要用户输入的地方,用户输入的是SQL语句的片段,最终用户输入的SQL片段与我们DAO中写的SQL语句合成一个完整的SQL语句!例如用户在登录时输入的用户名和密码都是为SQL语句的片段 -
防止SQL攻击
过滤用户输入的数据中是否包含非法字符;
分步校验!先使用用户名来查询用户,如果查找到了,再比较密码;
使用PreparedStatement -
preparedStatement:预编译声明,是Statement的子接口
- 使用Connection的prepareStatement(String sql):即创建它时就让它与一条SQL模板绑定
- 调用PreparedStatement的setXXX()系列方法为问号设置值
- 调用executeUpdate()或executeQuery()方法
String sql = “select * from tab_student where s_number=?”; PreparedStatement pstmt = con.prepareStatement(sql); pstmt.setString(1, “S_1001”); ResultSet rs = pstmt.executeQuery(); rs.close(); pstmt.clearParameters(); pstmt.setString(1, “S_1002”); rs = pstmt.executeQuery();
完整代码
try {
//注册驱动,com.mysql.jdbc.Driver已被弃用
Class.forName("com.mysql.cj.jdbc.Driver");
//获得链接
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test","root","1234567890");
//获取语句执行者
Statement st = conn.createStatement();
//获取结果集
ResultSet rs = st.executeQuery("select * from category");
//获取结果
while(rs.next()){
String cid = rs.getString("cid");
String cname = rs.getString(2);
System.out.println(cid+":"+cname);
}
rs.close();
st.close();
conn.close();
} catch (Exception e) {
// 异常获取
e.printStackTrace();
}
//规范代码,无论是否出现异常,都要关闭ResultSet、Statement、Connection
Connection con = null;
Statement stmt = null;
ResultSet rs = null;
try {
con = getConnection(url);
stmt = con.createStatement();
String sql = "select * from user";
rs = stmt.executeQuery(sql);
while(rs.next()) {
String username = rs.getString(1);
String password = rs.getString(2);
System.out.println(username + ", " + password);
}
} catch(Exception e) {
throw new RuntimeException(e);
} finally {
try {
if(rs != null) rs.close();
if(stmt != null) stmt.close();
if(con != null) con.close();
} catch(SQLException e) {}
}
可能存在的问题
- mysql时区报错
myslq默认是美国的时区
在mysql安装目录下,打开mysql.ini文件,添加default-time-zone=’+08:00’字样,然后,一定要重新启动mysql服务
生产环境中的JDBC–properties配置文件
- 创建后缀为properties的文件
- 文件内容
jdbc.driver=com.mysql.jdbc.Driver jdbc.url=... jdbc.user=... jdbc.password=...
- 使用ResourceBundle加载文件
private static driver; ... ResourceBundle rb = ResourceBundle.getBundle(文件名); driver = bundle.getString(jdbc.driver); ...
- Properties对象
//假定当前类为jdbcutils //1. 使用类加载ClassLoader加载src资源 InputStream is = jdbcutils.class.getClassLoader().getResourceAsStream(配置文件名); //2. 加载当前类同包下的资源,如果需要从src资源开始必须填写/ InputStream is2 = jdbcutils.class.getClassLoader().getResourceAsStream(/配置文件名); //使用properties处理流 Properties props = new Properties(); props.load(is); //调用getProperty(key) driver = props.getProperty("jdbc.driver"); ....
JDBC连接池
- 获得连接和释放资源非常耗费系统资源
自定义连接池
- 实现java.sql.DataSource接口
- 使用list存放多个连接对象
public class MyDataSource implements DataSource{ // 创建一个List集合用于存放多个连接对象. private List<Connection> list = new ArrayList<Connection>(); // 在程序开始的时候,初始化几个连接,将连接存放到list中. //可以将初始化过程放在static代码块中 public MyDataSource() { // 初始化3个连接: for(int i=1;i<=3;i++){ try{ Class.forName("com.mysql.cj.jdbc.Driver"); Connection conn = DriverManager.getConnection(url); list.add(conn); } catch(Exception e){ throw new RuntimeException(e); } } } @Override // 获得连接的方法: public Connection getConnection() throws SQLException { if(list.size() <= 0){ //进行连接创建 .... } Connection conn = list.remove(0); return conn; } // 归还连接的方法: public void addBack(Connection conn){ list.add(conn); } ... }
- 存在的问题
- 如果用户没有将连接归还而是直接调用close关闭,连接池中将出现无连接可用
- 解决
- 继承,控制类的构造
- 装饰者模式:包装对象和被包装对象实现相同的接口
- 动态代理
//继承与装饰模式案例 /** * 继承的方式增强一个类中某个方法: */ class Man{ public void run(){ System.out.println("跑...."); } } class SuperMan extends Man{ public void run(){ // super.run(); System.out.println("飞...."); } } /** * 使用装饰者的方式完成类的方法的增强 */ interface Waiter{ public void server(); } class Waiteress implements Waiter{ @Override public void server() { System.out.println("服务..."); } } class WaiteressWrapper implements Waiter{ private Waiter waiter; public WaiteressWrapper(Waiter waiter) { this.waiter = waiter; } @Override public void server() { System.out.println("微笑..."); // this.waiter.server(); } }
//改善后的连接池 public class MyConnection implements Connection{ private Connection conn; private List<Connection> list; public MyConnection(Connection conn,List<Connection> list) { this.conn = conn; this.list = list; } @Override public void close() throws SQLException { list.add(conn); } ... } 连接池的getConnection方法: @Override // 获得连接的方法: public Connection getConnection() throws SQLException { if(list.size() <= 0){ for(int i=1;i<=3;i++){ Connection conn = JDBCUtils.getConnection(); list.add(conn); } } Connection conn = list.remove(0); MyConnection myConn = new MyConnection(conn, list); return myConn; }
DBCP连接池
- 引入DBCP连接池的jar包
- 编写DBCP代码
- 设置参数
- 配置文件设置参数
//手动方式 Connection conn = null; PreparedStatement stmt = null; ResultSet rs = null; BasicDataSource dataSource = new BasicDataSource(); dataSource.setDriverClassName("com.mysql.jdbc.Driver"); dataSource.setUrl("jdbc:mysql:///web_07"); dataSource.setUsername("root"); dataSource.setPassword("123"); try{ // 获得连接: conn = dataSource.getConnection(); // 编写SQL: String sql = "select * from category"; // 预编译SQL: stmt = conn.prepareStatement(sql); // 执行SQL: rs = stmt.executeQuery(); while(rs.next()){ System.out.println(rs.getInt("cid")+" "+rs.getString("cname")); } }catch(Exception e){ e.printStackTrace(); }finally{ JDBCUtils.release(rs,stmt, conn); } //----------------配置文件方式------------------------- Connection conn = null; PreparedStatement stmt = null; ResultSet rs = null; Properties properties = new Properties(); try{ properties.load(new FileInputStream("src/dbcpconfig.properties")); DataSource dataSource = BasicDataSourceFactory.createDataSource(properties); // 获得连接: conn = dataSource.getConnection(); // 编写SQL: String sql = "select * from category"; // 预编译SQL: stmt = conn.prepareStatement(sql); // 执行SQL: rs = stmt.executeQuery(); while(rs.next()){ System.out.println(rs.getInt("cid")+" "+rs.getString("cname")); } }catch(Exception e){ e.printStackTrace(); }finally{ JDBCUtils.release(rs,stmt, conn); }
C3P0
- 与DBCP相似
配置文件<?xml version="1.0" encoding="UTF-8"?> <c3p0-config> <default-config> <property name="driverClass">com.mysql.jdbc.Driver</property> <property name="jdbcUrl">jdbc:mysql:///web08</property> <property name="user">root</property> <property name="password">root</property> <property name="initialPoolSize">5</property> <property name="maxPoolSize">20</property> </default-config> <named-config name="itheima"> <property name="driverClass">com.mysql.jdbc.Driver</property> <property name="jdbcUrl">jdbc:mysql:///web08</property> <property name="user">root</property> <property name="password">root</property> </named-config> </c3p0-config>
public class JDBCUtils2 { private static final ComboPooledDataSource DATA_SOURCE =new ComboPooledDataSource(); /** * 获得连接的方法 */ public static Connection getConnection(){ Connection conn = null; try { conn = DATA_SOURCE.getConnection(); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } return conn; } ...
JavaBean
- 属性
- 需要实现接口java.io.Serializable,通常省略
- 提供私有字段
- 提供getter、setter方法
- 提供无参构造
DButils
-
核心功能
- QueeryRunner提供对sql语句操作的API
- ResultSetHandle接口,定义select操作后,怎样粉装结果集
- DbUtils类,定义了关闭资源与事务处理的方法
-
QueryRunner核心类
- QueeryRunner(DataSource ds),提供数据源(连接池),底层自动维护连接connection
- update(String sql,object … params),执行更新数据
- query(string sql,ResultSetHandle<T> rsh ,Object …params),执行查询
-
ResultSetHandle
ArrayHandler:将结果集的第一条记录封装到一个数组中ArrayListHandler:将结果集中的每一条记录都封装到一个object数组中,将这些数组封装到list集合中
MapHandler:单行处理器!把结果集转换成Map<String,Object>,其中列名为键!
MapListHandler:多行处理器!把结果集转换成List<Map<String,Object>>;
BeanHandler:单行处理器!把结果集转换成Bean,该处理器需要Class参数,即Bean的类型;
BeanListHandler:多行处理器!把结果集转换成List<Bean>;
ColumnListHandler:多行单列处理器!把结果集转换成List<Object>,使用ColumnListHandler时需要指定某一列的名称或编号
ScalarHandler:单行单列处理器!把结果集转换成Object。一般用于聚集查询,例如select count(*) from tab_student。
@Test
public void fun1() throws SQLException {
DataSource ds = JdbcUtils.getDataSource();
QueryRunner qr = new QueryRunner(ds);
String sql = "select * from tab_student where number=?";
Map<String,Object> map = qr.query(sql, new MapHandler(), "S_2000");
System.out.println(map);
}
@Test
public void fun2() throws SQLException {
DataSource ds = JdbcUtils.getDataSource();
QueryRunner qr = new QueryRunner(ds);
String sql = "select * from tab_student";
List<Map<String,Object>> list = qr.query(sql, new MapListHandler());
for(Map<String,Object> map : list) {
System.out.println(map);
}
}
@Test
public void fun3() throws SQLException {
DataSource ds = JdbcUtils.getDataSource();
QueryRunner qr = new QueryRunner(ds);
String sql = "select * from tab_student where number=?";
Student stu = qr.query(sql, new BeanHandler<Student>(Student.class), "S_2000");
System.out.println(stu);
}
@Test
public void fun4() throws SQLException {
DataSource ds = JdbcUtils.getDataSource();
QueryRunner qr = new QueryRunner(ds);
String sql = "select * from tab_student";
List<Student> list = qr.query(sql, new BeanListHandler<Student>(Student.class));
for(Student stu : list) {
System.out.println(stu);
}
}
@Test
public void fun5() throws SQLException {
DataSource ds = JdbcUtils.getDataSource();
QueryRunner qr = new QueryRunner(ds);
String sql = "select * from tab_student";
List<Object> list = qr.query(sql, new ColumnListHandler("name"));
for(Object s : list) {
System.out.println(s);
}
}
@Test
public void fun6() throws SQLException {
DataSource ds = JdbcUtils.getDataSource();
QueryRunner qr = new QueryRunner(ds);
String sql = "select count(*) from tab_student";
Number number = (Number)qr.query(sql, new ScalarHandler());
int cnt = number.intValue();
System.out.println(cnt);
}