#JDBC从菜鸟版到大神版
##基础知识
程序获取连接,向数据库发出更新指令,数据库做相应改变
程序获取连接,向数据库发出获取指令,数据库做相应返回,程序需要将返回的resultSet中的信息使用具体类来接受(即将返回的属性值赋值给我们new的类的对象),多条数据就使用list来装。
##第一版(菜鸟版)
操作方式:
加载驱动、获取连接、获取语句、执行代码、释放资源,另外,要熟悉resultSet这个存放查询结果的类。
操作特点:这个版本的所有代码都是硬编码,无论是sql语句,还是执行方式,均毫无逻辑,只处于外行简单了解程序连接数据库操作的阶段。
##第二版
操作方式:
采用DAO思想,将增删改查等操作进行方法的封装,对不同表格写出不同的DAO类,并且对硬编码问题有了一点点改善。
特点:将之前对jdbc的模糊概念清晰化,逻辑化,将解决方案分为四个模块:(以学生表格为例)Student类,dao接口,dao实现类,dao测试类
我们使用类就可以在测试类里体验dao的特点:我们要增删改查时只需要先new一个具体的Student对象或者查询方法时传入一个参数即可完成jdbc操作,以下是实现类实例:
@Override
@Test
public void save(Student student) {
String sql="INSERT INTO t_student(name,age) VALUES("+ student.getName()+","+student.getAge()+")";
StringBuilder sb=new StringBuilder();
sb.append("INSERT INTO t_student01(name,age) values(");
sb.append("'"+student.getName()+"',");
sb.append(student.getAge());
sb.append(")");
Connection connection=null;
Statement statement=null;
try{
//1、贾
Class.forName("com.mysql.jdbc.Driver");
//2、连
connection=DriverManager.getConnection("jdbc:mysql:///jdbcdemo?useSSL=false","root","admin");
//3、欲
statement=connection.createStatement();
//4、执
System.out.println(sb.toString());
statement.executeUpdate(sb.toString());
}catch (Exception e){
e.printStackTrace();
}finally {
//5、释
try{
if (statement!=null){
statement.close();
}
}catch (SQLException e){
e.printStackTrace();
}finally {
try{
if (connection!=null){
connection.close();
}
}catch (SQLException e){
e.printStackTrace();
}
}
}
}
@Override
public List<Student> list() {
Connection connection=null;
Statement statement=null;
String sql="SELECT * FROM t_student01";
List list=new LinkedList();
try{
//1、贾
Class.forName("com.mysql.jdbc.Driver");
//2、连
connection=DriverManager.getConnection("jdbc:mysql:///jdbcdemo?useSSL=false","root","admin");
//3、欲
statement=connection.createStatement();
//4、执
ResultSet resultSet=statement.executeQuery(sql);
while (resultSet.next()){
Student student=new Student();
student.setAge(resultSet.getInt("age"));
student.setId(resultSet.getLong("id"));
student.setName(resultSet.getNString("name"));
list.add(student);
}
}catch (Exception e){
e.printStackTrace();
}
try{
//5、释
if (statement!=null){
statement.close();
}
}catch (SQLException e){
e.printStackTrace();
}finally {
try {
if (connection!=null){
connection.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
return list;
}
以下是测试类:
@Test
public void testSave(){
Student student1=new Student();
student1.setName("西门官人");
student1.setAge(28);
dao.save(student1);
}
@Test
public void TestList() {
System.out.println(dao.list());
}
##第三版
操作目的:
1、由于每次都要加载驱动,连接数据库,这些是重复的操作,我们要减少。
2、数据库连接用户名,密码,连接的数据库的类型都是写在代码内的,不便于后期人员改动维护,要解决这个问题
操作方式:
1、因此封装成一个Util工具类,负责被dao类调用(负责加载驱动,生成连接)
2、建立properties文件,db.properties文件的解析见代码:
url=jdbc:mysql:///jdbcdemo?useSSL=false
driver=com.mysql.jdbc.Driver
username=root
password=admin
以下是Util类代码:
注意:为了节省程序运行时间,我们使驱动只加载一次,解决方案是将代码写在静态代码块内。
static {
try{
ClassLoader loader=Thread.currentThread().getContextClassLoader();
InputStream inputStream= loader.getResourceAsStream("db.properties");
Properties properties=new Properties();
properties.load(inputStream);
driver=properties.getProperty("driver");
password=properties.getProperty("password");
url=properties.getProperty("url");
username=properties.getProperty("username");
Class.forName(driver);
System.out.println(properties);
}catch (Exception e){
e.printStackTrace();
}
}
以下是实现类代码
public void save(Student student) {
String sql="INSERT INTO t_student(name,age) VALUES("+ student.getName()+","+student.getAge()+")";
StringBuilder sb=new StringBuilder();
sb.append("INSERT INTO t_student01(name,age) values(");
sb.append("'"+student.getName()+"',");
sb.append(student.getAge());
sb.append(")");
Connection connection=null;
Statement statement=null;
try{
//1、贾
Class.forName(JdbcUtil.driver);
//2、连
connection=DriverManager.getConnection(JdbcUtil.url,JdbcUtil.username,JdbcUtil.password);
//3、欲
statement=connection.createStatement();
//4、执
System.out.println(sb.toString());
statement.executeUpdate(sb.toString());
}catch (Exception e){
e.printStackTrace();
}finally {
//5、释
JdbcUtil.close(connection,statement,null);
}
}
@Override
public List<Student> list() {
Connection connection=null;
Statement statement=null;
String sql="SELECT * FROM t_student01";
List list=new LinkedList();
ResultSet resultSet=null;
try{
//1、贾
Class.forName(JdbcUtil.driver);
//2、连
connection=DriverManager.getConnection(JdbcUtil.url,JdbcUtil.username,JdbcUtil.password);
//3、欲
statement=connection.createStatement();
//4、执
resultSet=statement.executeQuery(sql);
while (resultSet.next()){
Student student=new Student();
student.setAge(resultSet.getInt("age"));
student.setId(resultSet.getLong("id"));
student.setName(resultSet.getNString("name"));
list.add(student);
}
}catch (Exception e){
e.printStackTrace();
}finally {
JdbcUtil.close(connection,statement,resultSet);
}
return list;
}
下面是测试类实例:
public void testSave(){
Student student1=new Student();
student1.setName("八神");
student1.setAge(28);
dao.save(student1);
}
@Test
public void TestList() {
System.out.println(dao.list());
}
##第四版
背景知识:数据库服务器在执行sql语句时先检查,再编译并且存放到缓存区,如果有完全相同的SQL语句,那么就直接用缓存区内的编译好的语句,可以大大节省时间。
操作目的:
1、提高sql执行效率
2、为了解决sql注入问题
操作方式:使用预编译类PreparedStatement,其实就是在上一版对于需要传参数的方法的sql的参数的位置加上一个占位符“?”,list方法不需要,因为他没有参数,每个表就对应一个list方法,就一段代码。
操作特点:提高了sql执行效率,解决了sql注入问题
以下是实现类代码:
public void save(Student student) {
String sql="INSERT INTO t_student01(name,age) VALUES(?,?)";
StringBuilder sb=new StringBuilder();
sb.append("INSERT INTO t_student01(name,age) values(");
sb.append("'"+student.getName()+"',");
sb.append(student.getAge());
sb.append(")");
Connection connection=null;
PreparedStatement psmt=null;
try{
//1、贾
Class.forName(JdbcUtil.driver);
//2、连
connection=DriverManager.getConnection(JdbcUtil.url, JdbcUtil.username, JdbcUtil.password);
//3、欲
psmt=connection.prepareStatement(sql);
psmt.setString(1,student.getName());
psmt.setInt(2,student.getAge());
//4、执
// System.out.println(sb.toString());
psmt.executeUpdate();
}catch (Exception e){
e.printStackTrace();
}finally {
//5、释
JdbcUtil.close(connection,psmt,null);
}
}
以下是测试类代码:
public void testSave(){
Student student1=new Student();
student1.setName("八神");
student1.setAge(28);
dao.save(student1);
}
##第五版
操作目的:增加sql执行效率
操作方式:加入批处理类的功能
操作特点:一批sql放到batch内,同时执行,不再是一条一条执行,大大节省时间
以下是代码:
@Test
public void testStatementBatch () throws SQLException {
long start=System.currentTimeMillis();
Connection connection= JdbcUtil.getConnection();
Statement statement=connection.createStatement();
for (int i=0;i<1000;i++){
String sql="insert into t_student01(name,age) values ('乔峰"+i+"',30)";
System.out.println(sql);
statement.addBatch(sql);
}
statement.executeBatch();
System.out.println(System.currentTimeMillis()-start+"ms");
JdbcUtil.close(connection,statement,null);
}
##第六版
操作目的:向数据库存入大数据类型,blob(拔萝卜)
操作方式:都是预编译,设置参数,这里在设置参数时需要使用IO流,在查看时也是使用IO流将文件复制到另一位置即可查看。
操作特点:需要使用IO流。
以下是添加方法和查看方法的代码:
@Test
//添加大数据
public void testBlob () throws SQLException, FileNotFoundException {
String sql="insert into t_jpg(name,heading) values (?,?)";
Connection connection= JdbcUtil.getConnection();
PreparedStatement psmt=connection.prepareStatement(sql);
psmt.setString(1,"乔峰");
psmt.setBlob(2,new FileInputStream("f:/mm.jpg"));
psmt.executeUpdate();
JdbcUtil.close(connection,psmt,null);
}
//查找大数据
@Test
public void testGet() throws SQLException, IOException {
String sql="select *from t_jpg where id=?";
Connection connection= JdbcUtil.getConnection();
PreparedStatement psmt=connection.prepareStatement(sql);
psmt.setLong(1,1L);
ResultSet resultSet=psmt.executeQuery();
if (resultSet.next()){
Blob blob= resultSet.getBlob("heading");
InputStream binaryStream=blob.getBinaryStream();
//保存
Files.copy(binaryStream, Paths.get("e:/mm2.jpg"));
}
JdbcUtil.close(connection,psmt,resultSet);
}
##第七版
背景需求:安装一个app,注册账号密码之后,需要完善资料,那么数据库那么多条资料怎么知道是向哪一条记录里添加信息呢,这就需要自动获取ID
操作目的:为了满足新的需求–插入一条数据,用户想要知道默认赋予的ID是多少
操作方式:使用statement的多参数方法
操作特点:解决了自动获取ID的需求
以下是代码:
public void testAutoKey() throws SQLException {
Connection connection= JdbcUtil.getConnection();
String sql="insert into t_student01 (name,age) values ('张三',15)";
Statement statement=connection.createStatement();
statement.executeUpdate(sql,Statement.RETURN_GENERATED_KEYS);
//装的就是单行单列的主键
ResultSet resultSet=statement.getGeneratedKeys();
if (resultSet.next()){
System.out.println(resultSet.getLong(1));
}
JdbcUtil.close(connection,statement,resultSet);
}
##第八版
背景知识:数据库操作分类:DDL(create这种)、DML(数据操作语言,增删改)、DQL(数据查询语言,查)
操作目的:我们上几个版本对于Student表格的操作,将方法分为增删改查四个方法,其中大量代码重复,因此,我们想将他们统一起来。
操作方式:建立一个Template类,这也是一个工具类。就是对实现类中的方法再次进行抽象。
操作特点:代码量大大减少,新的工具类Template需要传入sql,Object。。。不确定长度数组。在使用时sql和数组元素都是由实现类中的方法定义
以下是工具类实现代码:
//还是针对student类
public class JdbcTemplate {
private JdbcTemplate(){}
public static void update(String sql,Object...params){
Connection connection=null;
PreparedStatement psmt=null;
try{
connection= DruidUtil.getConnection();
psmt=connection.prepareStatement(sql);
if (params!=null&¶ms.length>0){
for (int i=0;i<params.length;i++){
psmt.setObject(i+1,params[i]);
}
}
psmt.executeUpdate();
}catch (Exception e){
e.printStackTrace();
}finally {
DruidUtil.close(connection,psmt,null);
}
}
public static List<Student> query(String sql,Object...params) {
Connection connection=null;
PreparedStatement statement=null;
List list=new LinkedList();
ResultSet resultSet=null;
try{
connection= DruidUtil.getConnection();
statement=connection.prepareStatement(sql);
//resultSet=statement.executeQuery();
if (params!=null&¶ms.length>0){
for (int i=0;i<params.length;i++){
statement.setObject(i+1,params[i]);
}
}
resultSet=statement.executeQuery();
while (resultSet.next()){
Student student=new Student();
student.setAge(resultSet.getInt("age"));
student.setId(resultSet.getLong("id"));
student.setName(resultSet.getNString("name"));
list.add(student);
}
}catch (Exception e){
e.printStackTrace();
}finally {
JdbcUtil.close(connection,statement,resultSet);
}
return list;
}
}
以下是实现类代码:
public class StudentDAOImplYubianyi implements IStudentDAO {
@Override
@Test
//DMLs三个方法
//添加方法
public void save(Student student) {
String sql="INSERT INTO t_student01(name,age) VALUES(?,?)";
Object[] params={student.getName(),student.getAge()};
JdbcTemplate.update(sql,params);
}
//删除方法
@Override
public void delete(Long id) {
String sql="DELETE FROM t_student01 WHERE id = ?";
JdbcTemplate.update(sql,id);
}
//修改方法
@Override
public void update(Student student) {
String sql="UPDATE t_student01 SET name=? ,age=? WHERE ID=?";
Object[] params={student.getName(),student.getAge()};
JdbcTemplate.update(sql,params);
}
//查找单条数据方法
@Override
public Student get(Long id) {
String sql="SELECT * FROM t_student01 WHERE id=?";
List<Student> list=JdbcTemplate.query(sql,id);
return list.size()>0 ?list.get(0):null;
}
//查找所有数据方法
@Override
public List<Student> list() {
String sql="SELECT * FROM t_student01";
return JdbcTemplate.query(sql);//没值就没值,不要加NULL
}
}
以下是测试代码:
public void TestUpdate(){
Student student=new Student();
student.setName("楚留香");
student.setAge(30);
student.setId(27L);
System.out.println(student.getName());
dao.update(student);
}
##第九版
操作目的:为DQL操作做进一步封装。使其适用于不同的表格。
操作方式:要更加通用,就得抽象性更高,让我们的Template更通用。DQL有get一条数据和list查询整个表格两个方法,get需要参数(ID)而list不需要参数,为了统一,我们在参数设置时仍然和上版本一样设置可变参数。工具类要让每个表格(类)提供自己的处理resultSet的方法,于是我们在实现类中加入一个内部类,这个类提供将resultSet转化为Student的list的方法。
操作特点:工具类更加通用,已经可以对不同的表格(类)达到了通用。
以下是实现类的增加的代码:
private class StudentResultSetHandler implements IResultsetHandler{
@Override
public List handle(ResultSet resultSet) throws SQLException {
List list=new LinkedList();
while (resultSet.next()){
Student student=new Student();
student.setAge(resultSet.getInt("age"));
student.setId(resultSet.getLong("id"));
student.setName(resultSet.getNString("name"));
list.add(student);
}
return list;
}
}
以下是工具类Template的变化后的query方法的代码:
public static List<Student> query(String sql, IResultsetHandler handler,Object...params) {
Connection connection=null;
PreparedStatement statement=null;
List list=new LinkedList();
ResultSet resultSet=null;
try{
connection= DruidUtil.getConnection();
statement=connection.prepareStatement(sql);
//resultSet=statement.executeQuery();
if (params!=null&¶ms.length>0){
for (int i=0;i<params.length;i++){
statement.setObject(i+1,params[i]);
}
}
resultSet=statement.executeQuery();
// while (resultSet.next()){
// Student student=new Student();
// student.setAge(resultSet.getInt("age"));
// student.setId(resultSet.getLong("id"));
// student.setName(resultSet.getNString("name"));
// list.add(student);
// }
list=handler.handle(resultSet);
}catch (Exception e){
e.printStackTrace();
}finally {
JdbcUtil.close(connection,statement,resultSet);
}
return list;
}
注意看,这里的注释部分的代码由每个类自己去实现,实现类只需要给工具类传入一个参数即可完成这种操作。
以下是测试类的代码:
@Test
public void TestGet(){
System.out.println(dao.get(2184L));
}
@Test
public void TestList() {
System.out.println(dao.list());
}
##第十版
操作目的:上面版本可以通用,但是只能返回list类型
操作方式:就是在实现类的实现的接口上加上泛型,实现类具体实现再加上具体的类型。这里不过是在实现类对resultSet的处理更加灵活,考虑到可能返回不同的类型的数据。于是在工具类Template的方法上也要加上泛型。
操作特点:使用泛型。
以下是接口代码:
public interface IResultsetHandler<T> {
//规定处理完resultset之后返回一个T集合
T handle(ResultSet resultSet) throws SQLException;
}
以下是实现类关键代码:
private class StudentResultSetHandler implements IResultsetHandler<List<Student>> {
@Override
public List<Student> handle(ResultSet resultSet) throws SQLException {
List<Student> list=new LinkedList();
while (resultSet.next()){
Student student=new Student();
student.setAge(resultSet.getInt("age"));
student.setId(resultSet.getLong("id"));
student.setName(resultSet.getNString("name"));
list.add(student);
}
return list;
}
}
以下是工具类代码:
public static <T> T query(String sql, IResultsetHandler<T> handler, Object...params) {
Connection connection=null;
PreparedStatement statement=null;
//List<T> list=new LinkedList<T>();
ResultSet resultSet=null;
try{
connection= DruidUtil.getConnection();
statement=connection.prepareStatement(sql);
//resultSet=statement.executeQuery();
if (params!=null&¶ms.length>0){
for (int i=0;i<params.length;i++){
statement.setObject(i+1,params[i]);
}
}
resultSet=statement.executeQuery();
return handler.handle(resultSet);
}catch (Exception e){
e.printStackTrace();
}finally {
JdbcUtil.close(connection,statement,resultSet);
}
return null;
}
##第十一版(大神版)
操作目的:上面版本的方法,每次都要创建那个内部类private class StudentResultSetHandler,于是我们希望可以建立一个通用的结果集resultSet处理器。
操作方式:我们不再使用那个内部类来处理结果集,而使用beanhandler来处理。
操作特点:使用BeanHandler,BeanHandlerList,使用了内省的机制。参数增加了一个class,并且使用的bean的遍历设置方法。
以下是单个和多个的handler类:
public class BeanHandler<T> implements IResultsetHandler<T> {
//beanHandler处理真是的javabean对象具体类型
private Class<T> type;
public BeanHandler(Class<T> type) {
this.type = type;
}
@Override
public T handle(ResultSet resultSet) throws IllegalAccessException, InstantiationException, SQLException, IntrospectionException, InvocationTargetException {
//1、通过type创建一个bean对象
T bean=null;
if (resultSet.next()){
bean=this.type.newInstance();
BeanInfo beanInfo=Introspector.getBeanInfo(this.type,Object.class);
PropertyDescriptor[] pds=beanInfo.getPropertyDescriptors();
for (PropertyDescriptor pd:pds){
String key=pd.getName();//表中的列名
Object value=resultSet.getObject(key);
pd.getWriteMethod().invoke(bean,value);
}
}
return bean;
}
}
###############################################
//将结果集处理成一个javabean对象集合,其中T表示一个具体的javabean对象
public class BeanListHandler<T> implements IResultsetHandler<List<T>> {
//beanHandler处理真是的javabean对象具体类型
private Class<T> type;
public BeanListHandler(Class<T> type) {
this.type = type;
}
@Override
public List<T> handle(ResultSet resultSet) throws IllegalAccessException, InstantiationException, SQLException, IntrospectionException, InvocationTargetException {
//1、通过type创建一个bean对象
List<T> list=new ArrayList<>();
while (resultSet.next()){ //开始是if,只能一次,记得改成while
T bean=this.type.newInstance();
BeanInfo beanInfo=Introspector.getBeanInfo(this.type,Object.class);
PropertyDescriptor[] pds=beanInfo.getPropertyDescriptors();
for (PropertyDescriptor pd:pds){
String key=pd.getName();//表中的列名
Object value=resultSet.getObject(key);
pd.getWriteMethod().invoke(bean,value);
}
list.add(bean);
}
return list;
}
}
工具类的代码
并没有任何变化,在此不做展示。
以下是实现类的关键代码:
//查找单条数据方法
@Override
public Student get(Long id) {
String sql="SELECT * FROM t_student01 WHERE id= ?";
Student student= JdbcTemplate.query(sql,new BeanHandler<Student>(Student.class),id);
return student;
}
//查找所有数据方法
@Override
public List<Student> list() {
String sql="SELECT * FROM t_student01";
return JdbcTemplate.query(sql,new BeanListHandler<>(Student.class));//没值就没值,不要加NULL
}
//之所以把内部类放在这里,就是为了每个表格一个dao,每个dao实现类负责自己定义自己的list的结构
//这样就能初步实现通用,但是由于返回的是一个list,所以不好
//内部类:处理学生查询结果集
// private class StudentResultSetHandler implements IResultsetHandler<List<Student>> {
//
// @Override
// public List<Student> handle(ResultSet resultSet) throws SQLException {
// List<Student> list=new LinkedList();
// while (resultSet.next()){
// Student student=new Student();
// student.setAge(resultSet.getInt("age"));
// student.setId(resultSet.getLong("id"));
// student.setName(resultSet.getNString("name"));
// list.add(student);
// }
// return list;
// }
// }
注释部分是减少的代码,get,list方法中参数有变动,注意观察。
下面Druid的连接池,也是JDBC的一部分,工具类代码如下:
public class DruidUtil {
private DruidUtil(){ }
private static DataSource ds;
public static void main(String[] args) {
}
//问题4:只执行一次,加载驱动
static {
try{
ClassLoader loader=Thread.currentThread().getContextClassLoader();
InputStream inputStream= loader.getResourceAsStream("druid.properties");
Properties properties=new Properties();
properties.load(inputStream);
ds= DruidDataSourceFactory.createDataSource(properties);
//ds= DruidDataSourceFactory.createDataSource(properties);
}catch (Exception e){
e.printStackTrace();
}
}
public static Connection getConnection(){
try{
return ds.getConnection();
}catch (Exception e){
e.printStackTrace();
}
return null;
}
//问题5 :重复代码;关闭资源
public static void close(Connection connection, Statement statement, ResultSet resultSet){
//5、释
try{
if (resultSet!=null){resultSet.close();}
}catch (SQLException e){
e.printStackTrace();
}finally {
try{
if (statement!=null){
statement.close();
}
}catch (SQLException e){
e.printStackTrace();
}finally {
try{
if (connection!=null){
connection.close();
}
}catch (SQLException e){
e.printStackTrace();
}
}
}
}
}
下面是测试类的,插入数据方法insert的代码:
@Test
public void testInsert() throws SQLException {
String sql="insert into t_student01(name,age) values(?,?)";
**Connection connection= DruidUtil.getConnection();**
PreparedStatement psmt=connection.prepareStatement(sql);
psmt.setString(1,"花满楼猪12");
psmt.setInt(2,28);
psmt.executeUpdate();
psmt.close();
connection.close();
}
学完这些,你就是精通JDBC的大神了。
(最后注意一点,运行JDBC程序首先检查服务是否启动。)