#原始的JDBC操作:
##原始JDBC源代码:
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* @ClassName: TestJDBC
* @Description: TODO(原始的JDBC操作数据库)
*/
public class TestJDBC {
public static void main(String[] args) {
// 数据库连接
Connection connection = null;
// 预编译的Statement,使用预编译的Statement提高数据库性能
PreparedStatement preparedStatement = null;
// 结果集
ResultSet resultSet = null;
try {
// 加载数据库驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// 通过驱动管理类获取数据库链接
connection = DriverManager
.getConnection(
"jdbc:mysql://localhost:3306/toutiao?characterEncoding=utf-8",
"root", "xxh171015");
// 定义sql语句 ?表示占位符
String sql = "select * from user where name = ?";
// 获取预处理statement
preparedStatement = connection.prepareStatement(sql);
// 设置参数,第一个参数为sql语句中参数的序号(从1开始),第二个参数为设置的参数值
preparedStatement.setString(1, "x");
// 向数据库发出sql执行查询,查询出结果集
resultSet = preparedStatement.executeQuery();
// 遍历查询结果集
while (resultSet.next()) {
System.out.println("id:"+resultSet.getString("id") + " name:"
+ resultSet.getString("name")+" head_url:"+resultSet.getString("head_url"));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 释放资源
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if (preparedStatement != null) {
try {
preparedStatement.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
##存在的问题
1、数据库连接,使用时就创建,不使用立即释放,对数据库进行频繁连接开启和关闭,造成数据库资源浪费,影响数据库性能。
解决方案:使用数据库连接池管理数据库连接。
2、将sql语句硬编码到java代码中,如果sql 语句修改,需要重新编译java代码,不利于系统维护。
解决方案:将sql语句配置在xml配置文件中,即使sql变化,不需要对java代码进行重新编译。
3、向preparedStatement中设置参数,对占位符号位置和设置参数值,硬编码在java代码中,不利于系统维护。
解决方案:将sql语句及占位符号和参数全部配置在xml中。
4、从resultSet中遍历结果集数据时,存在硬编码,将获取表的字段进行硬编码,,不利于系统维护。
解决方案:将查询的结果集,自动映射成java对象。
MyBatis是什么?
MyBatis是一个优秀的持久层ORM(Object Relational Mapping)框架,它对jdbc的操作数据库的过程进行封装,使开发者只需要关注 SQL 本身,而不需要花费精力去处理例如注册驱动、创建connection、创建statement、手动设置参数、结果集检索等jdbc繁杂的过程代码。
Mybatis通过==xml==或注解的方式将要执行的各种statement(statement、preparedStatemnt、CallableStatement)配置起来,并通过java对象和statement中的sql进行映射生成最终执行的sql语句,最后由mybatis框架执行sql并将结果映射成java对象并返回。
##MyBatis架构图
架构图简单讲解:
1、mybatis配置SqlMapConfig.xml,此文件作为mybatis的全局配置文件,配置了mybatis的运行环境等信息。
mapper.xml文件即sql映射文件,文件中配置了操作数据库的sql语句。此文件需要在SqlMapConfig.xml中加载;
2、通过mybatis环境等配置信息构造SqlSessionFactory即会话工厂;
3、由会话工厂创建sqlSession即会话,操作数据库需要通过sqlSession进行;
4、mybatis底层自定义了Executor执行器接口操作数据库,Executor接口有两个实现,一个是基本执行器、一个是缓存执行器;
5、Mapped Statement也是mybatis一个底层封装对象,它包装了mybatis配置信息及sql映射信息等。mapper.xml文件中一个sql对应一个Mapped Statement对象,sql的id即是Mapped statement的id;
6、Mapped Statement对sql执行输入参数进行定义,包括HashMap、基本类型、pojo,Executor通过Mapped Statement在执行sql前将输入的java对象映射至sql中,输入参数映射就是jdbc编程中对preparedStatement设置参数;
7、Mapped Statement对sql执行输出结果进行定义,包括HashMap、基本类型(String、Integer)、pojo,Executor通过Mapped Statement在执行sql后将输出结果映射至java对象中,输出结果映射过程相当于jdbc编程中对结果的解析处理过程。
简单举例:
这个例子中使用注解的方式使用mybatis
拿UserService中的登录功能举例:
public Map<String, Object> login(String username, String password){
Map<String,Object> map=new HashMap<String,Object>();
if (StringUtils.isBlank(username)){
map.put("msgname","用户名不能为空");
return map;
}
if (StringUtils.isBlank(password)){
map.put("msgpwd","密码不能为空");
return map;
}
//这句是我们要注意的,用到了被依赖注入的userDAO中的selectByName()方法
User user = userDAO.selectByName(username);
if(user==null){
map.put("msgname","用户名不存在");
return map;
}
if(!ToutiaoUtil.MD5(password+user.getSalt()).equals(user.getPassword())){
map.put("msgpwd","密码不正确");
return map;
}
map.put("userId", user.getId());
String ticket=addLoginTicket(user.getId());
map.put("ticket",ticket);
return map;
}
UserDAO中:
String TABLE_NAME = "user";
String INSET_FIELDS = " name, password, salt, head_url ";
String SELECT_FIELDS = " id, name, password, salt, head_url";
@Select({"select ", SELECT_FIELDS, " from ", TABLE_NAME, " where id=#{id}"})
User selectById(int id);
上面中@Select注解中一个sql对应一个Mapped Statement对象,Mapped Statement对sql执行输入参数进行定义,包括HashMap、基本类型、pojo(java对象),这里的输入对象是(Integer)id;
Executor通过Mapped Statement在执行sql前将输入的java对象映射至sql中,输入参数映射就是jdbc编程中对preparedStatement设置参数(这步中的映射操作是,上述由@Select注解的内容,经过java对象最后映射成了sql语句,其实就是将#{id}映射成方法中的形参id);然后执行映射后的sql语句;
会被映射成如下:
select id, name, password, salt, head_url from user where id=(方法中传入的id)
Executor执行完sql后,会将输出结果映射至java对象中,输出结果映射过程相当于jdbc编程中对结果的解析处理过程。
我们从UserDAO的selectById()方法中就可以看出来,最后返回的是User对象。
MyBatis的执行流程
1 Resources
首先Mybaitis自己的一个Resources类会去调用一个叫getResourceAsStream()的方法
加载配置MyBatis的核心配置文件,得到一个流对象(InputStream类)。(本质上还是走的java.lang包下的ClassLoader类加载器的getResourceAsStream)
是在mybatis自己的包下:
package org.apache.ibatis.io;
/*
* Returns a resource on the classpath as a Stream object
*
* @param resource The resource to find
* @return The resource
* @throws java.io.IOException If the resource cannot be found or read
*/
public static InputStream getResourceAsStream(String resource) throws IOException {
return getResourceAsStream(null, resource);
}
/*
* Returns a resource on the classpath as a Stream object
*
* @param loader The classloader used to fetch the resource
* @param resource The resource to find
* @return The resource
* @throws java.io.IOException If the resource cannot be found or read
*/
public static InputStream getResourceAsStream(ClassLoader loader, String resource) throws IOException {
InputStream in = classLoaderWrapper.getResourceAsStream(resource, loader);
if (in ** null) {
throw new IOException("Could not find resource " + resource);
}
return in;
}
package java.lang;
public abstract class ClassLoader {
public InputStream getResourceAsStream(String name) {
URL url = getResource(name);
try {
return url != null ? url.openStream() : null;
} catch (IOException e) {
return null;
}
}
}
2 SqlSessionFactoryBuilder
有了这个流对象,下面执行这个方法
package org.apache.ibatis.session;
public class SqlSessionManager implements SqlSessionFactory, SqlSession {
public static SqlSessionManager newInstance(InputStream inputStream) {
return new SqlSessionManager(new SqlSessionFactoryBuilder().build(inputStream, null, null));
}
}
package org.apache.ibatis.session;
public class SqlSessionFactoryBuilder {
public SqlSessionFactory build(InputStream inputStream) {
return build(inputStream, null, null);
}
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
}
会先去创建一个SqlSessionFactoryBuilder对象(构建者对象),调用build()方法,传入第一步我们得到的那个流对象。
在这个过程中,首先,MyBatis会先去创建一个XMLConfigBuilder解析器对象,
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse());
解析我们刚才传进来的那个流对象,
package org.apache.ibatis.builder.xml;
public class XMLConfigBuilder extends BaseBuilder {
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
}
解析完成之后,返回一个configuration对象,这个对象里面封装了解析之后我们配置的信息
正好这个configuration对象,就是我们这个方法创建(I)SqlSessionFactory类型对象需要的参数
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
返回的就是一个实现了SqlSessionFactory接口的==DefaultSqlSessionFactory==对象
3 SqlSesionFactory
有了这个SqlSesionFactory实现类的对象之后,我们调用openSession()的方法,这个过程中
package org.apache.ibatis.session.defaults;
public class DefaultSqlSessionFactory implements SqlSessionFactory {
public SqlSession openSession() {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
}
需 要 通 过TransactionFactory 生成 Transaction 对象, 并且还需要创建核心执行器 Executor对象,基于这些条件,最终创建了实现SqlSession接口的DefaultSqlSession对象
4 SqlSession
之后, 通过 SqlSession 对象执行相应的操作, 如果执行成功, 调用 commit 方法提交事务; 如果失败, 调用rollback 方法事务回滚. 最后, 调用 close 方法关闭session 资源.
##总结 MyBatis运行过程中涉及到的类和接口:
Resources(C) 类
用于加载MyBatis核心配置文件
XMLConfigBuilder(C) 类
用于解析xml文件(核心配置文件)
Configuration(C)类
用于存放xml文件解析后的结果
DefaultSqlSessionFactory(C)类
是SqlSessionFactory(I)的实现类,创建时需要使用Configruation对象作为参数
SqlSession(I)接口
是MyBatis操作的核心
DefaultSqlSession(C)类
是SqlSession接口的实现类
TransactionFactory(I)接口
用于生产Trasaction对象(关于事务的)
Transaction(I) 接口
用于表示操作数据库的事务对象
Executor(I)接口
是MyBatis的核心执行器,类似于JDBC中的Statement,常用的实现类是SimpleExecutor