传统jdbc操作
连接数据库
打开Statement对象
通过Statement执行sql,结果返回ResultSet对象
使用ResultSet对象读取数据转换成POJO对象
关闭数据库相关资源,ResultSet、Statement、Connection、
//创建一个数据库的连接
private static Connection getConnection() {
Connection connection = null;
try {
Class.forName("com.mysql.jdbc.Driver");//加载用户驱动
String url = "jdbc:mysql://localhost:3306/mybatis";//连接数据库的地址
String user = "root";//数据库的用户名
String password = "root";//数据库的密码
connection = DriverManager.getConnection(url, user, password);//得到一个数据库的连接
} catch (ClassNotFoundException e) {
// TODO 自动生成的 catch 块
System.out.println(JdbcExample.class.getName() + "数据库驱动包未找到!");
return null;
} catch (SQLException e) {
// TODO 自动生成的 catch 块
System.out.println(JdbcExample.class.getName() + "SQL语句有问题,无法查询成功!");
return null;
}
return connection;//返回该连接
}
/***
*
* @param rs 查看结果集是滞关闭
* @param stmt 预处理SQL是否关闭
* @param conn 数据库连接是否关闭
*/
private void close(ResultSet rs, Statement stmt, Connection conn) {
try {
if (rs != null) {
rs.close();
}
} catch (SQLException e) {
System.out.println(JdbcExample.class.getName() + "ResultSet 关闭失败!");
}
try {
if (stmt != null) {
stmt.close();
}
} catch (SQLException e) {
System.out.println(JdbcExample.class.getName() + "Statement 关闭失败!");
}
try {
if (conn != null) {
conn.close();
}
} catch (SQLException e) {
System.out.println(JdbcExample.class.getName() + "Connection 关闭失败!");
}
}
/**
*
*获取用户
*/
public User getUser(int id) {
Connection connection = getConnection();//得到该数据库的连接
PreparedStatement ps = null;//声明一个null的预处理的Statement
ResultSet rs = null;//声明一个结果集,用来存放SQL的查询后的结果
try {
ps = connection.prepareStatement("select * from user where id=?");//对查询的User表的SQL进行预处理编译
ps.setInt(1, id);//把参数Id设值到数据的条件中
rs = ps.executeQuery();//执行查询语句。把结果返回到ResultSet结果集中
while (rs.next()) {//遍历从结果集中取数
int user_id = rs.getInt("id");//取出Statement的用户id
String username = rs.getString("username");//取出Statement的用户名
Date birthday = rs.getDate("birthday");//取出Statement的生日
String sex = rs.getString("sex");//取出Statement的性别
String address = rs.getString("address");//取出Statement的用户地址
User user = new User();//创建一个User类的实体对象POJO
user.setId(user_id);//存放在user对象中
user.setUsername(username);
user.setBirthday(birthday);
user.setSex(sex);
user.setAddress(address);
return user;
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
this.close(rs, ps, connection);
}
return null;
}
太多的弊端了,就冗长的代码和硬编码这两项,我们就不能接受。
自定义持久层框架
封装jdbc的代码,实现数据库配置化,sql与代码分离,参数与返回结果自动映射。这就是mybatis的功能。以下是思路:
1.配置文件,sqlMapConfig.xml配置数据库源与相应的mapper文件的路径;xxxMapper.xml文件就是相关的sql文件。
2.配置解析相关,在SqlSessionFactoryBuilder中使用XMLConfigBuilder解析两个配置文件,生成configuration对象,该对象存储datasource与mappedStatement。
3.获取sqlsession,其中封装了相关的jdbc执行代码。根据需求直接调用即可,可选择使用getMapper()方法,代理模式解决硬编码的问题,防止用户输错的调用的方法。
扩展
我们之前先简单完成了查询的操作,现在要将增删改的操作补充上。
思路:
1.整理jdbc的增删改操作;
2.查看mybatis源码对于增删改的实现,大概理清楚我们需要实现什么;
3.扩展simpleExecutor方法与sqlsession.getMapper方法。
第一步整理jdbc的增删改操作
连接操作
private static Connection getConn() {
String driver = "com.mysql.jdbc.Driver";
String url = "jdbc:mysql://localhost:3306/samp_db";
String username = "root";
String password = "";
Connection conn = null;
try {
Class.forName(driver); //classLoader,加载对应驱动
conn = (Connection) DriverManager.getConnection(url, username, password);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
return conn;
}
插入操作
private static int insert(Student student) {
Connection conn = getConn();
int i = 0;
String sql = "insert into students (Name,Sex,Age) values(?,?,?)";
PreparedStatement pstmt;
try {
pstmt = (PreparedStatement) conn.prepareStatement(sql);
pstmt.setString(1, student.getName());
pstmt.setString(2, student.getSex());
pstmt.setString(3, student.getAge());
i = pstmt.executeUpdate();
pstmt.close();
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
return i;
}
更新操作
private static int update(Student student) {
Connection conn = getConn();
int i = 0;
String sql = "update students set Age='" + student.getAge() + "' where Name='" + student.getName() + "'";
PreparedStatement pstmt;
try {
pstmt = (PreparedStatement) conn.prepareStatement(sql);
i = pstmt.executeUpdate();
System.out.println("resutl: " + i);
pstmt.close();
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
return i;
}
删除操作
private static int delete(String name) {
Connection conn = getConn();
int i = 0;
String sql = "delete from students where Name='" + name + "'";
PreparedStatement pstmt;
try {
pstmt = (PreparedStatement) conn.prepareStatement(sql);
i = pstmt.executeUpdate();
System.out.println("resutl: " + i);
pstmt.close();
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
return i;
}
第二步查看mybatis源码对于增删改的实现
首先要观察的是sqlsession接口中的增删改方法
public class DefaultSqlSession implements SqlSession {
public interface SqlSession extends Closeable {
int insert(String statement, Object parameter);
int update(String statement, Object parameter);
int delete(String statement, Object parameter);
...
}
接下来是DefaultSqlSession的实现
@Override
public int insert(String statement) {
return insert(statement, null);
}
@Override
public int insert(String statement, Object parameter) {
return update(statement, parameter);
}
@Override
public int update(String statement) {
return update(statement, null);
}
@Override
public int update(String statement, Object parameter) {
try {
// 标记 dirty ,表示执行过写操作
dirty = true;
// 获得 MappedStatement 对象
MappedStatement ms = configuration.getMappedStatement(statement);
// 执行更新操作
return executor.update(ms, wrapCollection(parameter));
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error updating database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
@Override
public int delete(String statement) {
return update(statement, null);
}
@Override
public int delete(String statement, Object parameter) {
return update(statement, parameter);
}
...
}
接下来是SimpleExecutor
public class SimpleExecutor extends BaseExecutor {
public SimpleExecutor(Configuration configuration, Transaction transaction) {
super(configuration, transaction);
}
@Override
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
// 创建 StatementHandler 对象
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
// 初始化 StatementHandler 对象
stmt = prepareStatement(handler, ms.getStatementLog());
// 执行 StatementHandler ,进行写操作
return handler.update(stmt);
} finally {
// 关闭 StatementHandler 对象
closeStatement(stmt);
}
}
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
// 传入参数创建StatementHanlder对象来执行查询
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
// 创建jdbc中的statement对象
stmt = prepareStatement(handler, ms.getStatementLog());
// 执行 StatementHandler ,进行读操作
return handler.query(stmt, resultHandler);
} finally {
// 关闭 StatementHandler 对象
closeStatement(stmt);
}
}
}
没办法必须进入StatementHandler
public class SimpleStatementHandler extends BaseStatementHandler {
public SimpleStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
super(executor, mappedStatement, parameter, rowBounds, resultHandler, boundSql);
}
@Override
public int update(Statement statement) throws SQLException {
String sql = boundSql.getSql();
Object parameterObject = boundSql.getParameterObject();
KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
int rows;
// 如果是 Jdbc3KeyGenerator 类型
if (keyGenerator instanceof Jdbc3KeyGenerator) {
// 执行写操作
statement.execute(sql, Statement.RETURN_GENERATED_KEYS);
// 获得更新数量
rows = statement.getUpdateCount();
// 执行 keyGenerator 的后置处理逻辑
keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject);
// 如果是 SelectKeyGenerator 类型
} else if (keyGenerator instanceof SelectKeyGenerator) {
// 执行写操作
statement.execute(sql);
// 获得更新数量
rows = statement.getUpdateCount();
// 执行 keyGenerator 的后置处理逻辑
keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject);
} else {
// 执行写操作
statement.execute(sql);
// 获得更新数量
rows = statement.getUpdateCount();
}
return rows;
}
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
String sql = boundSql.getSql();
// 执行查询
statement.execute(sql);
// 处理返回结果
return resultSetHandler.handleResultSets(statement);
}
}
我们自己之前实现以及刚刚的jdbc增删改操作,都与mybatis使用的不同。所以这里讲一下JDBC中Statement接口提供的execute、executeQuery和executeUpdate之间的区别。
Statement 接口提供了三种执行 SQL 语句的方法:executeQuery、executeUpdate 和 execute。使用哪一个方法由 SQL 语句所产生的内容决定。
方法executeQuery
用于产生单个结果集的语句,例如 SELECT 语句。 被使用最多的执行 SQL 语句的方法是 executeQuery。这个方法被用来执行 SELECT 语句,它几乎是使用最多的 SQL 语句。
方法executeUpdat
用于执行 INSERT、UPDATE 或 DELETE 语句以及 SQL DDL(数据定义语言)语句,例如 CREATE TABLE 和 DROP TABLE。INSERT、UPDATE 或 DELETE 语句的效果是修改表中零行或多行中的一列或多列。executeUpdate 的返回值是一个整数,指示受影响的行数(即更新计数)。对于 CREATE TABLE 或 DROP TABLE 等不操作行的语句,executeUpdate 的返回值总为零。
方法execute:
用于执行返回多个结果集、多个更新计数或二者组合的语句。
第三步扩展simpleExecutor方法与sqlsession.getMapper方法
public class simpleExecutor implements Executor {
//mybatis中连接并没有直接获取连接
@Override
public int update(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception {
// 1. 注册驱动,获取连接
Connection connection = configuration.getDataSource().getConnection();
//2.转换sql语句
String sql = mappedStatement.getSql();
//解析后的sql
BoundSql boundSql = getBoundSql(sql);
// 3.获取预处理对象:preparedStatement
PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSqlText());
// 4. 设置参数
//获取到了参数的全路径,类型
String paramterType = mappedStatement.getParamterType();
Class<?> paramtertypeClass = getClassType(paramterType);
List<ParameterMapping> parameterMappingList = boundSql.getParameterMappingList();
for (int i = 0; i < parameterMappingList.size(); i++) {
ParameterMapping parameterMapping = parameterMappingList.get(i);
//参数名
String content = parameterMapping.getContent();
//反射到对应的属性
Field declaredField = paramtertypeClass.getDeclaredField(content);
//暴力访问
declaredField.setAccessible(true);
Object o = declaredField.get(params[0]);
//setObject 会根据传入参数的类型进行调用相应的set方法
preparedStatement.setObject(i+1,o);
}
int row;
// 5. 执行sql
row = preparedStatement.executeUpdate();
//connect.close
//preparedStatement.close
return row;
}
}
public class DefaultSqlSession implements SqlSession {
private Configuration configuration;
public DefaultSqlSession(Configuration configuration) {
this.configuration = configuration;
}
//模仿mybatis的实现统一使用update方法进行执行操作,毕竟都是使用excuteUpdate方法
@Override
public int insert(String statementid, Object... params) throws Exception{
return update( statementid, params) ;
}
@Override
public int delete(String statementid, Object... params) throws Exception{
return update( statementid, params) ;
}
//模仿query的步骤
@Override
public int update(String statementid, Object... params) throws Exception {
//统一在Executor中执行sql
simpleExecutor simpleExecutor = new simpleExecutor();
//获取MappedStatement
MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementid);
//传入数据库配置,sqlStatement,参数
int row = simpleExecutor.update(configuration, mappedStatement, params);
return row;
}
}
修改完毕,我们就实现了增删改查的所有操作
使用getMapper()方法实现增删改查
也就是使用动态代理实现我们的dao层的mapper接口,在动态代理中使用DefaultSqlSession的方法去执行增删改查,代码如下
public class DefaultSqlSession implements SqlSession {
private Configuration configuration;
public DefaultSqlSession(Configuration configuration) {
this.configuration = configuration;
}
...
@Override
public <T> T getMapper(Class<?> mapperClass) {
// 使用JDK动态代理来为Dao接口生成代理对象,并返回
Object proxyInstance = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(), new Class[]{mapperClass}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 底层都还是去执行JDBC代码 //根据不同情况,来调用selctList或者selectOne
// 准备参数 1:statmentid :sql语句的唯一标识:namespace.id= 接口全限定名.方法名
// 方法名:findAll
String methodName = method.getName();
String className = method.getDeclaringClass().getName();
String statementId = className+"."+methodName;
// 准备参数2:params:args
// 获取被调用方法的返回值类型
Type genericReturnType = method.getGenericReturnType();
//判断是执行select 还是执行增删改
MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);
if(mappedStatement.getResultType()==null){
return update(statementId, args);
}else{
// 判断是否进行了 泛型类型参数化
if(genericReturnType instanceof ParameterizedType){
List<Object> objects = selectList(statementId, args);
return objects;
}else{
return selectOne(statementId,args);
}
}
}
});
return (T) proxyInstance;
}
}
在这里我们其实需要判断改sql的类型,以方便我们去执行不同的sql。简单处理,判断没有返回参数的就是增删改操作。