1 原生JDBC编程的总结
1.1 JDBC编程步骤
- 注册驱动
- 获取数据库连接对象(Connection)
- 定义sql
- 获取数据库预编译对象并传入定义的sql语句
- 如果是带条件的sql语句需要设置参数
- 执行sql语句
- 封装结果集
- 消费结果集
1.2 封装JDBCUtil
第一步:导入MySQL的pom依赖
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
第二步:创建配置文件jdbc.properties,记录连接数据库的参数
driver=com.mysql.jdbc.Driver
url=jdbc:mysql:///db1
user=root
password=333
第三步:封装JDBCUtil
- 在类加载时读取配置文件并加载驱动
- 封装获取数据库连接的方法
- 封装释放资源的方法
package com.phk.utils;
import java.io.*;
import java.sql.*;
import java.util.Properties;
/**
* jdbc工具类,用于创建数据库连接
*/
public class JDBCUtil {
private static String url = null;
private static String user = null;
private static String password = null;
static {
try {
// 1.读取配置文件
Properties properties = new Properties();
InputStream in = JDBCUtil.class.getClassLoader().getResourceAsStream("jdbc.properties");
properties.load(in);
String driver = properties.getProperty("driver");
url = properties.getProperty("url");
user = properties.getProperty("user");
password = properties.getProperty("password");
// 2.注册驱动
Class.forName(driver);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
/**
* 获取连接
* @return
* @throws SQLException
*/
public static Connection getConnection() throws SQLException {
return DriverManager.getConnection(url,user,password);
}
/**
* 释放资源
*/
public static void close(Connection connection,Statement preparedStatement,ResultSet resultSet){
if (connection != null){
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (preparedStatement != null){
try {
preparedStatement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (resultSet != null){
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
public static void close(Connection connection,Statement statement){
close(connection,statement,null);
}
}
第四步:创建Application测试JDBCUtil
- 利用JDBCUtil获取连接
- 定义sql
- 获取数据库预编译对象
- 设置参数
- 执行sql语句
- 处理结果集
- 在finally中使用JDBCUtil释放资源
- 消费结果集
package com.phk;
import com.phk.pojo.Dept;
import com.phk.utils.JDBCUtil;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
/**
* 测试
*/
public class Application {
public static void main(String[] args) {
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
List<Dept> deptList = new ArrayList<Dept>();
try {
// 获取连接
connection = JDBCUtil.getConnection();
// 定义sql
String sql = "select * from dept where id in(?,?)";
// 获取数据库预编译对象
preparedStatement = connection.prepareStatement(sql);
// 设置sql的参数
preparedStatement.setInt(1,10);
preparedStatement.setInt(2,20);
// 执行sql
resultSet = preparedStatement.executeQuery();
// 处理结果集
while (resultSet.next()){
int id = resultSet.getInt(1);
String dname = resultSet.getString(2);
String loc = resultSet.getString(3);
Dept dept = new Dept(id, dname, loc);
deptList.add(dept);
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
// 释放资源
JDBCUtil.close(connection,preparedStatement,resultSet);
}
// 消费结果集
System.out.println(deptList);
}
}
1.3 引入Druid连接池,优化JDBCUtil
在实际项目中,频繁的创建连接和释放连接效率特别低,用数据库连接池来管几个或多个连接可以降低获取连接和释放连接效率低下的问题。
第一步:引入Druid连接池pom的依赖
<dependencies>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.21</version>
</dependency>
</dependencies>
第二步:创建druid.properties配置文件,用于存放数据库连接池的参数
配置文件中一定不要出现多余的空格,否则会报错,另外键的名称一定要正确,不能自己定义,前面我用的user,而druid中使用的是username
# druid连接池的参数配置,更多配置信息可自行搜索
url=jdbc:mysql://localhost:3306/db1
username=root
password=333
# 启动程序时,在连接池中初始化多少个连接
initialSize=5
# 连接池中最多支持多少个活动会话
maxActive=10
# 程序向连接池中请求连接时,超过maxWait的值后,认为本次请求失败,即连接池没有可用连接,单位毫秒,设置-1时表示无限等待
maxWait=3000
第三步:封装DruidUtil
- 在类加载的时候读取配置文件并创建Druid数据源
- 封装获取数据源的方法
- 封装获取数据库连接的方法
- 封装释放资源的方法
package com.phk.utils;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
/**
* @Description Druid管理数据库连接,工具类
* @Autor Peng hk
* @Date 2020/8/28
**/
public class DruidUtil {
private static DataSource dataSource;
static {
// 读取配置文件
Properties properties = new Properties();
InputStream in = DruidUtil.class.getClassLoader().getResourceAsStream("druid.properties");
try {
properties.load(in);
// 将参数直接传入
dataSource = DruidDataSourceFactory.createDataSource(properties);
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 获取数据源对象
* @return
*/
public static DataSource getDataSource(){
return dataSource;
}
/**
* 从druid连接池中获取数据库连接对象
* @return
* @throws SQLException
*/
public static Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
/**
* 释放资源:用连接池管理的连接,在释放资源的时候是直接返回给连接池
* @param conn
* @param stmt
* @param rs
*/
public static void close(Connection conn, Statement stmt, ResultSet rs) {
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {}
}
if (stmt != null) {
try {
stmt.close();
} catch (SQLException e) {}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {}
}
}
// 6.重载关闭方法
public static void close(Connection conn, Statement stmt) {
close(conn, stmt, null);
}
}
第四步:测试
@Test
public void DruidUtilTest(){
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
List<Dept> deptList = new ArrayList<Dept>();
try {
// 获取连接
connection = DruidUtil.getConnection();
// 定义sql
String sql = "select * from dept where id in(?,?)";
// 获取数据库预编译对象
preparedStatement = connection.prepareStatement(sql);
// 设置sql的参数
preparedStatement.setInt(1,10);
preparedStatement.setInt(2,20);
// 执行sql
resultSet = preparedStatement.executeQuery();
// 处理结果集
while (resultSet.next()){
int id = resultSet.getInt(1);
String dname = resultSet.getString(2);
String loc = resultSet.getString(3);
Dept dept = new Dept(id, dname, loc);
deptList.add(dept);
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
// 释放资源
DruidUtil.close(connection,preparedStatement,resultSet);
}
// 消费结果集
System.out.println(deptList);
}
1.4 原生JDBC编程存在的问题
1.数据库连接,使用时就创建,不使用立即释放,对数据库进行频繁连接开启和关闭,造成数据库资源浪费,影响数据库性能。
设想:使用数据库连接池管理数据库连接。这里我们已经使用数据库连接池解决了
2.将sql语句硬编码到java代码中,如果sql语句修改,需要重新编译java代码,不利于系统维护。
设想:将sql语句配置在xml配置文件中,即使sql变化,不需要对java代码进行重新编译。
3.向preparedStatement中设置参数,对占位符号位置和设置参数值,硬编码在java代码中,不利于系统维护。
设想:将sql语句及占位符号和参数全部配置在xml中。
4.从resultSet中遍历结果集数据时,存在硬编码,将获取表的字段进行硬编码,不利于系统维护。
设想:将查询的结果集,自动映射成java对象。
2 Mybatis框架简介
-
mybatis是一个持久层的框架,是apache下的顶级项目。
mybatis托管到goolecode下,再后来托管到github下(https://github.com/mybatis/mybatis-3/releases)。
-
mybatis让程序将主要精力放在sql上,通过mybatis提供的映射方式,自由灵活生成(半自动化,大部分需要程序员编写sql)满足需要sql语句。
-
mybatis可以将向 preparedStatement中的输入参数自动进行输入映射,将查询结果集灵活映射成java对象。(输出映射)
Mybatis基于这些优点,很好的解决了原生JDBC存在的问题。
2.1 Mybatis框架原理
2.2 Mybatis框架执行流程
1、配置mybatis的配置文件,mybatis-config.xml(名称不固定)
2、通过配置文件,加载mybatis运行环境,创建SqlSessionFactory会话工厂(SqlSessionFactory在实际使用时按单例方式)
3、通过SqlSessionFactory创建SqlSession。SqlSession是一个面向用户接口(提供操作数据库方法),实现对象是线程不安全的,建议sqlSession应用场合在方法体内。
4、调用sqlSession的方法去操作数据。如果需要提交事务,需要执行SqlSession的commit()方法。
5、释放资源,关闭SqlSession
2.3 Mybatis开发DAO层方法
1.原始dao 的方法(基本不使用)
- 需要程序员编写dao接口和实现类
- 需要在dao实现类中注入一个SqlSessionFactory工厂
2.mapper代理开发方法(建议使用)
只需要程序员编写mapper接口(就是dao接口)。
程序员在编写mapper.xml(映射文件)和mapper.java需要遵循一个开发规范:
- mapper.xml中namespace就是mapper.java的类全路径。
- mapper.xml中statement的id和mapper.java中方法名一致。
- mapper.xml中statement的parameterType指定输入参数的类型和mapper.java的方法输入参数类型一致
- mapper.xml中statement的resultType指定输出结果的类型和mapper.java的方法返回值类型一致。
mybatis-config.xml配置文件:可以配置properties属性、别名、mapper加载。
2.3.1 原生开发Dao层
1.导入Mybatis的pom依赖,准备sql
create table `dept` (
`id` int (11),
`dname` varchar (150),
`loc` varchar (150)
);
insert into `dept` (`id`, `dname`, `loc`) values('10','教研部','北京');
insert into `dept` (`id`, `dname`, `loc`) values('20','学工部','上海');
insert into `dept` (`id`, `dname`, `loc`) values('30','销售部','广州');
insert into `dept` (`id`, `dname`, `loc`) values('40','财务部','深圳');
2.创建实体类(Dept)
package com.phk.mybatis.pojo;
public class Dept {
private int id;
private String dname;
private String loc;
public Dept(int id, String dname, String loc) {
this.id = id;
this.dname = dname;
this.loc = loc;
}
@Override
public String toString() {
return "Dept{" +
"id=" + id +
", dname='" + dname + '\'' +
", loc='" + loc + '\'' +
'}';
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getDname() {
return dname;
}
public void setDname(String dname) {
this.dname = dname;
}
public String getLoc() {
return loc;
}
public void setLoc(String loc) {
this.loc = loc;
}
}
3.创建数据库配置文件(db.propterties)
driver=com.mysql.jdbc.Driver
url=jdbc:mysql:///db1
username=root
password=333
4.创建DeptDao接口
package com.phk.mybatis.dao;
import com.phk.mybatis.pojo.Dept;
import java.util.List;
/**
* @Description TODO
* @Autor Peng hk
* @Date 2020/8/28
**/
public interface DeptDao {
/**
* 根据id查询
* @param id
* @return
*/
List<Dept> queryDeptByID(int id);
}
5.创建deptDao接口实现类
package com.phk.mybatis.dao.impl;
import com.phk.mybatis.dao.DeptDao;
import com.phk.mybatis.pojo.Dept;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import java.util.List;
/**
* @Description TODO
* @Autor Peng hk
* @Date 2020/8/28
**/
public class DeptDaoImpl implements DeptDao {
private SqlSessionFactory sqlSessionFactory;
public DeptDaoImpl(SqlSessionFactory sqlSessionFactory) {
this.sqlSessionFactory = sqlSessionFactory;
}
public List<Dept> queryDeptByID(int id) {
SqlSession session = sqlSessionFactory.openSession();
List<Dept> deptList = session.selectList("com.phk.mybatis.dao.DeptDao.queryDeptByID", id);
session.close();
return deptList;
}
}
6.测试
@Test
public void DeptDaoTest() throws IOException {
// 读取mybatis配置文件
InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
// 创建sqlSessionFactory对象
SqlSessionFactory build = new SqlSessionFactoryBuilder().build(in);
// 创建dao的实现类
DeptDaoImpl deptDao = new DeptDaoImpl(build);
List<Dept> deptList = deptDao.queryDeptByID(10);
System.out.println(deptList);
}
输出结果:[Dept{id=10, dname=‘教研部’, loc=‘北京’}]
2.3.2Mapper代理开发(常用的)
这种方式不用我们自己去实现dao接口。
1.创建DeptMapper(在mybatis开发中,一般就用Mapper来表示了)
package com.phk.mybatis.mapper;
import com.phk.mybatis.pojo.Dept;
import java.util.List;
/**
* @Description TODO
* @Autor Peng hk
* @Date 2020/8/28
**/
public interface DeptMapper {
/**
* 查询所有
* @return
*/
List<Dept> queryAllDept();
}
2.编写DeptMapper.xml
注意在mybatis-config.xml文件中将该文件添加到映射器
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.phk.mybatis.mapper.DeptMapper">
<select id="queryAllDept" resultType="Dept">
select *from dept
</select>
</mapper>
3.测试
@Test
public void DeptDaoTest2() throws IOException {
// 读取mybatis配置文件
InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
// 创建sqlSessionFactory对象
SqlSessionFactory build = new SqlSessionFactoryBuilder().build(in);
// 创建SqlSession对象
SqlSession session = build.openSession();
// 获取代理对象DeptMapper
DeptMapper deptMapper = session.getMapper(DeptMapper.class);
// 执行方法
List<Dept> depts = deptMapper.queryAllDept();
System.out.println(depts);
}
输出结果:[Dept{id=10, dname=‘教研部’, loc=‘北京’}, Dept{id=20, dname=‘学工部’, loc=‘上海’}, Dept{id=30, dname=‘销售部’, loc=‘广州’}, Dept{id=40, dname=‘财务部’, loc=‘深圳’}]
补充:基于注解的开发
基于注解的开发我们不用编写接口的映射文件(即DeptMapper),通过mybatis提供的注解可直接将sql语句写在接口方法上
1.在DeptDao中添加一个查询总记录数的方法(queryTotalCount)
/**
* 查询总记录数,使用Select注解
* @return
*/
@Select("select count(*) from dept")
int queryTotalCount();
2.测试
@Test
public void DeptDaoTest3() throws IOException {
// 读取mybatis配置文件
InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
// 创建sqlSessionFactory对象
SqlSessionFactory build = new SqlSessionFactoryBuilder().build(in);
// 创建SqlSession对象
SqlSession session = build.openSession();
// 获取代理对象DeptMapper
DeptMapper deptMapper = session.getMapper(DeptMapper.class);
// 执行方法
int totalCount = deptMapper.queryTotalCount();
System.out.println(totalCount);
}
输出结果:4
3 总结
编写xml好还是使用注解好?
- 使用注解可以简化开发,对于简单的业务注解开发可以提高效率。
- 但实际的业务一般都比较复杂,比如多表连接查询,做对象的结果集映射等,注解好像就有点力不从心了。
- 另外注解直接将sql语句写在代码中,一旦sql出现修改,就会改动源代码,不利于维护。
- xml可以做更多高级的配置,结果集映射,sql片段复用等。
- 因此我个人认为编写xml更好,当然对于一些业务简单的情况下,注解的确可以大大提高开发效率。
一些结论和思考
-
代理对象内部调用
selectOne
或selectList
- 如果mapper方法返回单个pojo对象(非集合对象),代理对象内部通过selectOne查询数据库。
- 如果mapper方法返回集合对象,代理对象内部通过selectList查询数据库。
-
mapper接口方法参数只能有一个是否影响系统开发
mapper接口方法参数只能有一个,系统是否不利于扩展维护?系统框架中,dao层的代码是被业务层公用的。即使mapper接口只有一个参数,可以使用包装类型的pojo满足不同的业务方法的需求。
注意:持久层方法的参数可以包装类型、map…等,service方法中建议不要使用包装类型(不利于业务层的可扩展)。