MyBatis核心流程
1、读取xml配置文件和注解中的配置信息。
2、构建sqlsessionFactory
3、打开sqlSession
4、获取mapper接口对象,通过SqlSession完成SQL解析,参数的映射,SQL的执行,结果的反射解析过程
这个案例写的不是很完善,我们重在理解原理。现在开始上代码
一、加入必要的jar
<!-- 数据库驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.22</version>
</dependency>
<!-- 依赖dom4j来解析mapper.xml -->
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
二、在resources配置相关配置文件(mybatis-config.xml)
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://xxx:3306/base?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC"/>
<property name="username" value="xxx"/>
<property name="password" value="xxx"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/spring/mapper/UserMapper.xml"/>
</mappers>
</configuration>
说明:这里的配置文件,主要是数据库的链接和Mapper的存放位置
三、我们先写个案例,然后跟着这个案例,一步一步完成。
这个案例是根据id,查询用户信息。
1、先写一个接口
public interface UserMapper {
User getUserById(Integer id);
}
2、再来一个mapper
<?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.spring.mapper.UserMapper">
<select id="getUserById" parameterType="java.lang.Integer" resultType="com.spring.mode.User">
select * from user where id = #{id}
</select>
</mapper>
四、我们根据上面的案例,看看,我们怎么实现
主要思想:首先根据配置文件获得数据库链接,这样我们就可以拿到访问数据库的方式,然后加载所有的Mapper中的sql到一个Map中,最后SqlSession完成SQL解析,参数的映射,SQL的执行,结果的反射解析过程。
1、主流程
//第一步,加载配置文件
InputStream inputStream=Main.class.getClassLoader().getResourceAsStream("mybatis-config.xml");
//第二步构建sqlsessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//第三步 打开sqlSession
SqlSession session = sqlSessionFactory.openSession();
//第四步 获取mapper接口对象
UserMapper userMapper= session.getMapper(UserMapper.class);
//第五步 调用mapper接口对象操作数据库
User user = userMapper.getUserById(1);
System.out.println(user);
//第六步 业务处理
2、加载配置文件,创建SqlSessionFactory
public class SqlSessionFactoryBuilder {
public SqlSessionFactory build(InputStream inputStream){
/**
注意这里 这个地方去读取配置文件的信息,封装到Configuration里面
*/
XmlUtil xmlUtil=new XmlUtil();
Configuration configuration=xmlUtil.loadConfig(inputStream);
return new SqlSessionFactory(configuration);
}
}
public class SqlSessionFactory {
private Configuration configuration;
public SqlSessionFactory(Configuration configuration){
this.configuration=configuration;
}
public SqlSession openSession() {
Executor executor=new Executor(configuration);
return new SqlSession(configuration,executor);
}
}
- 我们来看下Configuration里面有什么
public class Configuration {
//主要放置数据库链接信息
private Environment environment;
//所有sql
private Map<String,MapperStatement> mapperStatementMap;
}
/**
* 数据库的信息
*/
public class Environment {
private String driver;
private String url;
private String username;
private String password;
}
/**
* Mapper里面的主要信息
*/
public class MapperStatement {
private String sourceId;
private String namespace;
private String id;
private String sql;
private String parameterType;
private String resultType;
}
- 解析配置文件,写的比较简陋,仅供参考
/**
* 主要解析配置文件
*/
public class XmlUtil {
Map<String,MapperStatement> mapperStatementMap=new ConcurrentHashMap<String, MapperStatement>();
Environment environment=new Environment();
public Configuration loadConfig(InputStream inputStream){
Configuration configuration=new Configuration();
//创建SAXReader对象
SAXReader saxReader = new SAXReader();
//通过read方法读取一个文件,转换成Document对象
Document document = null;
try {
document = saxReader.read(inputStream);
} catch (DocumentException e) {
e.printStackTrace();
}
Element rootElement = document.getRootElement();
Element environments = rootElement.element("environments").element("environment").element("dataSource");
List<Element> property = environments.elements("property");
for(Element el:property){
String name = el.attribute("name").getData().toString();
String value = el.attribute("value").getData().toString();
if("driver".equals(name)){
environment.setDriver(value);
}else if("url".equals(name)){
environment.setUrl(value);
}else if("username".equals(name)){
environment.setUsername(value);
}else if("password".equals(name)){
environment.setPassword(value);
}
}
Element mappers = rootElement.element("mappers");
List<Element> mapperList =mappers.elements("mapper");
for(Element el:mapperList){
String name = el.attribute("resource").getData().toString();
URL resource = XmlUtil.class.getClassLoader().getResource(name);
File file = new File(resource.getFile());
loadMapper(file);
}
configuration.setEnvironment(environment);
configuration.setMapperStatementMap(mapperStatementMap);
return configuration;
}
/* private void loadMappersInfo() {
URL resource = null;
resource = XmlUtil.class.getClassLoader().getResource(MAPPER_CONFIG_LOCATION);
//获取指定文件夹信息
File file = new File(resource.getFile());
if (file.isDirectory()) {
File[] mappers = file.listFiles();
//遍历文件夹下所有的mapper.xml文件,解析后,注册到configuration中
for (File mapper : mappers) {
loadMapper(mapper);
}
}
}*/
/**
* //TODO 对mapper.xml文件解析
*
* @param mapper
* @return void
**/
private void loadMapper(File mapper) {
//创建SAXReader对象
SAXReader saxReader = new SAXReader();
//通过read方法读取一个文件,转换成Document对象
Document document = null;
try {
document = saxReader.read(mapper);
} catch (DocumentException e) {
e.printStackTrace();
}
//获取根节点元素对象<mapper>
Element rootElement = document.getRootElement();
//获取命名空间
String namespace = rootElement.attribute("namespace").getData().toString();
//获取子节点<select>标签
List<Element> selects = rootElement.elements("select");
//遍历select节点,将信息记录到MappedStatement对象,并登记到Configuration对象中
for (Element element : selects) {
MapperStatement statement = new MapperStatement();
String id = element.attribute("id").getData().toString();
String resultType = element.attribute("resultType").getData().toString();
String parameterType = element.attribute("parameterType").getData().toString();
//读取sql语句信息
String sql = element.getData().toString();
String sourceId = namespace + "." + id;
//给MappedStatement对象赋值
statement.setSourceId(sourceId);
statement.setNamespace(namespace);
statement.setResultType(resultType);
statement.setSql(sql);
statement.setParameterType(parameterType);
statement.setId(id);
mapperStatementMap.put(sourceId,statement);
}
}
}
3、sqlSession,这里主要是写了数据库连接池,还有sql执行器
- 通过数据库链接获得数据源
public interface MyDataSourceInterface extends DataSource {
@Override
default Connection getConnection() throws SQLException {
return null;
}
@Override
default Connection getConnection(String username, String password) throws SQLException {
return null;
}
@Override
default <T> T unwrap(Class<T> iface) throws SQLException {
return null;
}
@Override
default boolean isWrapperFor(Class<?> iface) throws SQLException {
return false;
}
@Override
default PrintWriter getLogWriter() throws SQLException {
return null;
}
@Override
default void setLogWriter(PrintWriter out) throws SQLException {
}
@Override
default void setLoginTimeout(int seconds) throws SQLException {
}
@Override
default int getLoginTimeout() throws SQLException {
return 0;
}
@Override
default Logger getParentLogger() throws SQLFeatureNotSupportedException {
return null;
}
}
自定义数据库链接
public class MyDataSource implements MyDataSourceInterface {
private Environment environment;
private List<Connection> pool;
private Connection conn=null;
private static MyDataSource instance=null;
private static final int POOL_SIZE=2;
private MyDataSource(Environment environment){
this.environment=environment;
pool=new ArrayList<Connection>(POOL_SIZE);
this.createConnection();
}
public static MyDataSource getInstance(Environment environment){
if(instance==null){
instance=new MyDataSource(environment);
}
return instance;
}
private void createConnection(){
for(int i=0;i<POOL_SIZE;i++){
try {
Class.forName(environment.getDriver());
conn= DriverManager.getConnection(environment.getUrl(),environment.getUsername(),environment.getPassword());
pool.add(conn);
}catch (Exception e){
}
}
}
@Override
public synchronized Connection getConnection(){
if(pool.size()>0){
Connection conn=pool.get(0);
pool.remove(conn);
return conn;
}else {
return null;
}
}
public synchronized void release(Connection conn){
pool.add(conn);
}
public synchronized void closePool(){
for(int i=0;i<POOL_SIZE;i++){
try {
conn=(Connection)pool.get(i);
conn.close();
pool.remove(i);
}catch (Exception e){
}
}
}
}
通过SqlSession查询数据
public class SqlSession {
private Configuration configuration;
private Executor executor;
public SqlSession(Configuration configuration, Executor executor) {
this.configuration = configuration;
this.executor = executor;
}
public <T> T getMapper(Class<T> clazz){
MapperProxy mapperProxy=new MapperProxy(this);
return (T)Proxy.newProxyInstance(clazz.getClassLoader(),new Class<?>[]{clazz},mapperProxy);
}
public <T> T selectOne(String statementKey,Object args) {
MapperStatement mapperStatement = configuration.getMapperStatementMap().get(statementKey);
List<T> resultList= executor.query(mapperStatement,args);
if(resultList!=null&&resultList.size()>1){
throw new RuntimeException("不止一个");
}else{
if(resultList!=null&&resultList.size()>0){
return resultList.get(0);
}else{
return null;
}
}
}
public <T> T selectList(Object[] args) {
return null;
}
public <T> T selectMap(Object[] args) {
return null;
}
}
可以看出其实真正执行查询的是executor,我们来研究下executor,‘
public class Executor {
private Configuration configuration;
private DataSource dataSource;
public Executor(Configuration configuration) {
this.configuration = configuration;
dataSource=MyDataSource.getInstance(configuration.getEnvironment());
}
public <T> List<T> query(MapperStatement mapperStatement, Object parameter) {
Connection connection=null;
PreparedStatement preparedStatement=null;
ResultSet resultSet=null;
List<T> resultList=new ArrayList<>();
try {
connection=dataSource.getConnection();
String sql=MyBatisUtil.parse(mapperStatement.getSql()).trim();
System.out.println("sql:"+sql);
preparedStatement=connection.prepareStatement(sql);
if(parameter instanceof Integer){
preparedStatement.setInt(1,(Integer)parameter);
}else if(parameter instanceof Double){
preparedStatement.setDouble(1,(Double)parameter);
}else if(parameter instanceof String ){
preparedStatement.setString(1,(String)parameter);
}
resultSet=preparedStatement.executeQuery();
handlerResultSet(resultSet,resultList,mapperStatement.getResultType());
}catch (Exception e){
}finally {
if(resultSet !=null){//关闭记录集
try{
resultSet.close();
} catch (SQLException e){
e.printStackTrace();
}
}
}
return resultList;
}
private <T> void handlerResultSet(ResultSet resultSet,List<T> resultList,String resultType){
try {
Class<T>clazz=(Class<T>)Class.forName(resultType);
ReflectionUtil.setProToBeanFromResult(resultSet,clazz,resultList);
}catch (Exception e){
}
}
}
可以看出底层是JDBC在工作,这里采用预编译sql,所以要处理sql
/**
* 将sql处理成预编译sql
*/
public class MyBatisUtil {
private static final String openToken="#{";
private static final String closeToken="}";
public static String parse(String text){
if(text == null || text.isEmpty()){
return null;
}
int start=text.indexOf(openToken);
if(start==-1){
return text;
}
char [] src=text.toCharArray();
int offset =0;
final StringBuilder builder=new StringBuilder();
StringBuilder expression=null;
while (start>-1){
if(start>0&&src[start-1]=='\\'){
builder.append(src,offset,start-offset-1).append(openToken);
offset=start+openToken.length();
}else {
if(expression ==null){
expression=new StringBuilder();
}else {
expression.setLength(0);
}
builder.append(src,offset,start-offset);
offset=start+openToken.length();
int end=text.indexOf(closeToken,offset);
while (end>-1){
if(end>offset&&src[end-1]=='\\'){
expression.append(src,offset,end-offset-1).append(closeToken);
offset=end+closeToken.length();
end=text.indexOf(closeToken,offset);
}else {
expression.append(src,offset,end-offset);
offset=end+closeToken.length();
break;
}
}
if(end==-1){
builder.append(src,start,src.length-start);
offset=src.length;
}else{
builder.append("?");
offset=end+closeToken.length();
}
}
start=text.indexOf(openToken,offset);
}
if(offset<src.length){
builder.append(src,offset,src.length-offset);
}
return builder.toString();
}
}
4、获取mapper接口对象
根据上面分析可以看出,可以通过动态代理完成接口数据的查询
UserMapper userMapper= session.getMapper(UserMapper.class);
public <T> T getMapper(Class<T> clazz){
MapperProxy mapperProxy=new MapperProxy(this);
return (T)Proxy.newProxyInstance(clazz.getClassLoader(),new Class<?>[]{clazz},mapperProxy);
}
我们看下代理类
public class MapperProxy implements InvocationHandler {
private SqlSession sqlSession;
public MapperProxy(SqlSession sqlSession) {
this.sqlSession = sqlSession;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Class<?> returnType = method.getReturnType();
if(Collection.class.isAssignableFrom(returnType)){
return sqlSession.selectList(args);
}else if(Map.class.isAssignableFrom(returnType)){
return sqlSession.selectMap(args);
}else{
String statementKey=method.getDeclaringClass().getName()+"."+method.getName();
return sqlSession.selectOne(statementKey,null==args?null:args[0]);
}
}
}
5、调用mapper接口对象操作数据库
User user = userMapper.getUserById(1);
6、测试下
public static void main(String[] args) {
//第一步,加载配置文件
InputStream inputStream=Main.class.getClassLoader().getResourceAsStream("mybatis-config.xml");
//第二步构建sqlsessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//第三步 打开sqlSession
SqlSession session = sqlSessionFactory.openSession();
//第四步 获取mapper接口对象
UserMapper userMapper= session.getMapper(UserMapper.class);
//第五步 调用mapper接口对象操作数据库
User user = userMapper.getUserById(1);
System.out.println(user);
//第六步 业务处理
}
评论区