闭关修炼180天--手写持久层框架(mybatis简易版)

抛砖引玉

首先先看一段传统的JDBC编码的代码实现:

//传统的JDBC实现
public static void main(String[] args) {
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
//加载数据库驱动
Class.forName("com.mysql.jdbc.Driver");
//通过驱动管理类获取数据库管理
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8","root","root");
//定义sql语句
String sql = "select * from user where username = ?";
//获取预处理对象statement
preparedStatement = connection.prepareStatement(sql);
//设置参数,第一个参数为sql语句中的参数的序号(从1开始),第二个参数为设置的参数值
preparedStatement.setString(1,"tom");
//像数据库发出sql执行查询,查询出结果集
resultSet = preparedStatement.executeQuery();
while (resultSet.next()){
int id = resultSet.getInt("id");
String username = resultSet.getString("username");
String password = resultSet.getString("password");
//将查询出的结果集封装进实体中
User user = new User();
user.setId(id);
user.setUsername(username);
user.setPassword(password);
}
} catch (Exception e) {
e.printStackTrace();
}finally {
//释放资源
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}

通过以上传统的JDBC操作数据库的代码可以发现,我们能总结出来以下几条问题:

  • 每执行一次sql都要建立一次数据库连接,数据库连接创建、释放频繁造成系统资源浪费,从而影响系统性能。

  • Sql编写在了代码中存在硬编码问题,实际上工作中sql变化是比较大的,每次都要修改代码,sql语句不易维护。

  • 使用preparedStatement向占有位符号传参数存在硬编码,因为sql语句的where条件不一定,可能 多也可能少,修改sql还要修改代码,系统不易维护。

  • 对结果集解析存在硬编码(查询列名),sql变化导致解析代码变化,系统不易维护,如果能将数据库 记录自动封装成pojo对象解析比较方便。

针对以上的几条JDBC问题,我们可以大致的延伸出以下几点的解决思路:

  • 使用数据库连接池初始化连接资源。

  • 将sql语句写在xml文件中,在代码中剥离出来单独维护。

  • 使用反射内省等技术,完成数据库的表字段和实体的属性的自动映射。

深入剖析

本次完成持久层框架的自定义编写便是从以上几个方面入手,来解决传统的JDBC存在的问题,在编写之前,我们首先要明白,框架属于开发的一个半成品,是我们在开发过程中可以直接拿来用的东西,我们在自定义编写时,什么代码是框架中所有的,什么代码属于使用端(一般开发人员)提供的,这点要想明白。经分析,大体划分如下:

使用端

  • 提供核心配置文件:

    • sqlMapConfig.xml文件,配置数据源等信息,同时引入Mapper.xml。
    • Mapper.xml文件,配置sql语句等信息。

框架端

  • 读取配置文件,将配置文件加载成字节输入流存放在内存中,准备好两个javaBean用来存储以后解析配置文件出来的数据。

    • Configuration:存放数据源信息dataBase、Map<String,MappedStatement>、key为唯一标识:namespace+id,value是sql相关信息实体。
    • MappedStatement:存放sql相关信息,包含id,sql语句,入参类型,返回值类型等。
  • 解析配置文件,创建SqlSessionFactoryBuild类,提供build(InputStream in)方法,用于构建SqlSessionFactory。

    • 使用dom4j技术解析xml配置文件,将解析出来的数据存放到javaBean中。
    • 创建SqlSessionFactory的实现类DefaultSqlSessionFactory
  • 生产会话对象。在SqlSessionFactory中提供openSession()方法,用于生产SqlSession。

  • 创建SqlSession接口及其实现类,用于封装crud方法。

    • selectList(String statementId,Object... param) 查询全部
    • selectOne(String statementId,Object... param) 查询单个
  • 执行实际的JDBC操作。创建Executor接口及其实现类SimpleExecutor,提供query(Configuration configuration, MappedStatement mappedStatement, Object... params)方法,完成实际的与数据库交互的工作。

    • 从连接池中获取连接
    • 处理sql语句
    • 设置参数
    • 封装结果集

涉及到的设计模式

  • 构建者模式
  • 工厂模式
  • 代理模式

代码实现

这里只贴出核心代码,全部代码请看我的码云:https://gitee.com/zang-chuanlei/FrameMyBatis.git

在这里需要创建两个项目:FrameMyBatistest和FrameMyBatis,其中FrameMyBatis_test代表的是使用端,FrameMyBatis代表的是框架端,项目结构如下:

1.在FrameMyBatis_test下面的resources目录下添加两个配置文件sqlMapConfig.xml和UserMapper.xml

<configuration>
<dataSource>
<property name="dataDriver" value="com.mysql.jdbc.Driver"></property>
<property name="dataUrl" value="jdbc:mysql:///mybatis"></property>
<property name="username" value="root"></property>
<property name="password" value="root"></property>
</dataSource>
<!--加载UserMapper.xml配置文件-->
<mapper resource="UserMapper.xml"></mapper>
</configuration>
<mapper namespace="com.zae.dao.UserDao">

    <select id="findAll" resultType="com.zae.entity.User">
select * from users
</select> <select id="findOne" resultType="com.zae.entity.User" paramterType="com.zae.entity.User">
select * from users where id=#{id} and username=#{username}
</select> </mapper>

2.给FrameMyBatis引入一些需要的坐标

<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.17</version>
</dependency>
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.12</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
</dependency>
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
<dependency>
<groupId>jaxen</groupId>
<artifactId>jaxen</artifactId>
<version>1.1.6</version>
</dependency> </dependencies>

3.在FrameMyBatis下创建Resources类,用于加载字节输入流。

import java.io.InputStream;

public class Resources {
/**
* 根据xml路径将xml文件加载成字节流,存放在内存中
* @param path
* @return
*/
public static InputStream getInputStreamByXml(String path){
InputStream resourceAsStream = Resources.class.getClassLoader().getResourceAsStream(path);
return resourceAsStream;
}
}

4.创建两个bean,Configuration和MappedStatement

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map; /**
* javaBean之一:用来装sqlMapConfig.xml文件的内容
*/
public class Configuration { /**
* 存储数据源连接信息
*/
private DataSource dataSource; /**
* 存储加载进来的mapper.xml里面的数据
* key = statementId = namespace+"."+id
* value = mapperStatement
*/
private Map<String,MapperStatement> mapperStatementMap = new HashMap<String, MapperStatement>(); public DataSource getDataSource() {
return dataSource;
} public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
} public Map<String, MapperStatement> getMapperStatementMap() {
return mapperStatementMap;
} public void setMapperStatementMap(Map<String, MapperStatement> mapperStatementMap) {
this.mapperStatementMap = mapperStatementMap;
}
}
/**
* javaBean-2用来装载mapper.xml的数据
* 一条sql语句信息封装在一个MapperStatement对象中
*/
public class MapperStatement { private String id; private String resultType; private String paramterType; private String sql; public String getId() {
return id;
} public void setId(String id) {
this.id = id;
} public String getResultType() {
return resultType;
} public void setResultType(String resultType) {
this.resultType = resultType;
} public String getParamterType() {
return paramterType;
} public void setParamterType(String paramterType) {
this.paramterType = paramterType;
} public String getSql() {
return sql;
} public void setSql(String sql) {
this.sql = sql;
}
}

5.创建SqlSessionFactoryBuild

import com.zae.config.XmlConfigBuilder;
import com.zae.pojo.Configuration; import java.io.InputStream; public class SqlSessionFactoryBuilder { /**
* 构建SqlSessionFactory工厂
* @param inputStream
* @return
*/
public SqlSessionFactory build(InputStream inputStream) throws Exception{
//完成xml文件的解析
XmlConfigBuilder xmlConfigBuilder = new XmlConfigBuilder();
Configuration configuration = xmlConfigBuilder.parasConfig(inputStream); DefaultSqlSessionFactory defaultSqlSessionFactory = new DefaultSqlSessionFactory(configuration);
return defaultSqlSessionFactory;
}
}

6.创建解析XML文件的两个专属类XmlConfigBuilder和XmlMapperBuilder

import com.mchange.v2.c3p0.ComboPooledDataSource;
import com.zae.io.Resources;
import com.zae.pojo.Configuration;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader; import java.io.InputStream;
import java.util.List;
import java.util.Properties; /**
* 解析sqlMapConfig.xml文件存放在javaBean中
*/
public class XmlConfigBuilder { private Configuration configuration; public XmlConfigBuilder(){
this.configuration = new Configuration();
} /**
* 解析sqlMapConfig.xml文件
* @param inputStream
* @return
*/
public Configuration parasConfig(InputStream inputStream) throws Exception{
SAXReader saxReader = new SAXReader();
Document document = saxReader.read(inputStream);
//获取根标签里面的内容
Element rootElement = document.getRootElement();
//获取所有的property标签里面的内容
List<Element> list = rootElement.selectNodes("//property");
//将数据库连接信息读取到properties文件中
Properties properties = new Properties();
for (Element element : list) {
//获取子标签里面的属性
String name = element.attributeValue("name");
String value = element.attributeValue("value");
properties.setProperty(name,value);
}
//创建数据库连接池
ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
comboPooledDataSource.setDriverClass(properties.getProperty("dataDriver"));
comboPooledDataSource.setJdbcUrl(properties.getProperty("dataUrl"));
comboPooledDataSource.setUser(properties.getProperty("username"));
comboPooledDataSource.setPassword(properties.getProperty("password"));
//给configuration里面的数据源属性赋值
configuration.setDataSource(comboPooledDataSource); //解析xl文件的数据
List<Element> mapperList = rootElement.selectNodes("//mapper");
for (Element element : mapperList) {
//获取到mapper.xml文件的路径
String resource = element.attributeValue("resource");
//获取mapper文件的输入流
InputStream mapperInputStream = Resources.getInputStreamByXml(resource);
XmlMapperBuilder xmlMapperBuilder = new XmlMapperBuilder(configuration);
xmlMapperBuilder.parasMapper(mapperInputStream);
} return configuration;
}
}
import com.zae.pojo.Configuration;
import com.zae.pojo.MapperStatement;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader; import java.io.InputStream;
import java.util.List;
/**
* 解析mapper.xml数据存放到javaBean中
*/
public class XmlMapperBuilder { private Configuration configuration; public XmlMapperBuilder(Configuration configuration){
this.configuration = configuration;
} public void parasMapper(InputStream mapperInputStream) throws Exception {
SAXReader saxReader = new SAXReader();
Document document = saxReader.read(mapperInputStream);
//获取根标签的数据-mapper
Element rootElement = document.getRootElement();
//获取命名空间
String namespace = rootElement.attributeValue("namespace");
//获取所有的select标签的内容
List<Element> elementList = rootElement.selectNodes("//select");
for (Element element : elementList) {
String id = element.attributeValue("id");
String resultType = element.attributeValue("resultType");
String paramterType = element.attributeValue("paramterType");
String sql = element.getTextTrim();
MapperStatement mapperStatement = new MapperStatement();
mapperStatement.setId(id);
mapperStatement.setResultType(resultType);
mapperStatement.setParamterType(paramterType);
mapperStatement.setSql(sql);
String key = namespace+"."+id;
configuration.getMapperStatementMap().put(key,mapperStatement);
}
}
}

7.创建sqlSessionFactory接口及DefaultSqlSessionFactory 实现类

public interface SqlSessionFactory {
SqlSession openSqlSession();
}
import com.zae.pojo.Configuration;

public class DefaultSqlSessionFactory implements SqlSessionFactory {

    private Configuration configuration;

    public DefaultSqlSessionFactory(Configuration configuration){
this.configuration = configuration;
} public SqlSession openSqlSession() {
return new DefaultSqlSession(configuration);
}
}

8.创建sqlSession 接口及DefaultSqlSession 实现类

import java.util.List;

public interface SqlSession {

    <E> List<E> findAll(String statementId,Object ... params) throws Exception;

    <T> T findOne(String statement,Object ...params)throws Exception;

    <T> T getMapper(Class<?> mapperClass);
}
import com.zae.pojo.Configuration;
import com.zae.pojo.MapperStatement; import java.lang.reflect.*;
import java.util.List;
import java.util.Map; public class DefaultSqlSession implements SqlSession {
private Configuration configuration; public DefaultSqlSession(Configuration configuration){
this.configuration = configuration;
} public <E> List<E> findAll(String statementId, Object... params) throws Exception{
Map<String, MapperStatement> statementMap = configuration.getMapperStatementMap();
MapperStatement mapperStatement = statementMap.get(statementId);
Executor executor = new SimpleExecutor();
List<Object> execute = executor.execute(configuration,mapperStatement,params);
return (List<E>) execute;
} public <T> T findOne(String statement, Object... params) throws Exception{
List<Object> objectList = findAll(statement, params);
if(objectList.size() == 1){
return (T) objectList.get(0);
}else{
throw new RuntimeException("返回参数不唯一或者为空");
}
} public <T> T getMapper(Class<?> aclass) {
Object object = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(), new Class[]{aclass}, new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//获取执行方法名
String methodName = method.getName();
//获取全限类名
String className = method.getDeclaringClass().getName();
//statementId
String statementId = className+"."+methodName;
//参数泛型化
Type type = method.getGenericReturnType();
if(type instanceof ParameterizedType){
//存在泛型,则调用findAll
List<Object> all = findAll(statementId, args);
return all;
}
return findOne(statementId,args);
}
}); return (T) object;
}
}

9.创建Executor接口及SimpleExecutor实现类,创建BoundSql实体,引入工具类。

import com.zae.pojo.Configuration;
import com.zae.pojo.MapperStatement; import java.util.List; public interface Executor {
/**
* jdbc处理方法
* @param configuration
* @param mapperStatement
* @param params
* @param <E>
* @return
*/
<E> List<E> execute(Configuration configuration, MapperStatement mapperStatement, Object ...params) throws Exception;
}
import com.zae.pojo.BorundSql;
import com.zae.pojo.Configuration;
import com.zae.pojo.MapperStatement;
import com.zae.utils.GenericTokenParser;
import com.zae.utils.ParameterMapping;
import com.zae.utils.ParameterMappingTokenHandler; import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.util.ArrayList;
import java.util.List; public class SimpleExecutor implements Executor {
public <E> List<E> execute(Configuration configuration, MapperStatement mapperStatement, Object... params) throws Exception{
//1.获取连接对象
Connection connection = configuration.getDataSource().getConnection();
//2.处理sql语句
String sql = mapperStatement.getSql();
BorundSql borundSql = dealSql(sql);
String sqlNow = borundSql.getSql();
List<ParameterMapping> mappingList = borundSql.getParameterMappingList();
//3.获取预处理对象
PreparedStatement preparedStatement = connection.prepareStatement(sqlNow);
//4.设置参数
Class<?> classByType = getClassByType(mapperStatement.getParamterType());
for(int i = 0;i<mappingList.size();i++){
String content = mappingList.get(i).getContent();
//根据属性名获取该属性信息
Field declaredField = classByType.getDeclaredField(content);
//设置暴力访问
declaredField.setAccessible(true);
//获取参数里面的值
Object value = declaredField.get(params[0]);
//设置参数
preparedStatement.setObject(i+1,value);
}
//5.处理返回结果集
ResultSet resultSet = preparedStatement.executeQuery();
Class<?> resultClass = getClassByType(mapperStatement.getResultType());
List resultList = new ArrayList();
while (resultSet.next()){
//生成该类的实例对象
Object object = resultClass.newInstance();
ResultSetMetaData metaData = resultSet.getMetaData();
for(int i = 1;i<=metaData.getColumnCount();i++){
//获取列的名称
String columnName = metaData.getColumnName(i);
//根据列的名称获取内容
Object value = resultSet.getObject(columnName);
//使用反射或内省,完成字段和数据库的映射
PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName,resultClass);
//获取写入方法
Method writeMethod = propertyDescriptor.getWriteMethod();
//调用invoke方法,将数据写入实体
writeMethod.invoke(object,value);
}
resultList.add(object);
}
return resultList;
} /**
* 处理sql,将sql中的#{}处理成?,并把#{}里面的字段保存下来
* @param sourceSql
* @return
*/
private BorundSql dealSql(String sourceSql){
ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler();
GenericTokenParser genericTokenParser = new GenericTokenParser("#{","}",parameterMappingTokenHandler);
String targetSql = genericTokenParser.parse(sourceSql);
List<ParameterMapping> parameterMappings = parameterMappingTokenHandler.getParameterMappings();
BorundSql borundSql = new BorundSql();
borundSql.setSql(targetSql);
borundSql.setParameterMappingList(parameterMappings);
return borundSql;
} /**
* 根据类的全路径获取类对象
* @param path
* @return
*/
private Class<?> getClassByType(String path) throws ClassNotFoundException {
if(path!=null){
Class<?> aClass = Class.forName(path);
return aClass;
}else{
return null;
}
}
}
import com.zae.utils.ParameterMapping;

import java.util.List;

public class BorundSql {
private String sql; private List<ParameterMapping> parameterMappingList; public String getSql() {
return sql;
} public void setSql(String sql) {
this.sql = sql;
} public List<ParameterMapping> getParameterMappingList() {
return parameterMappingList;
} public void setParameterMappingList(List<ParameterMapping> parameterMappingList) {
this.parameterMappingList = parameterMappingList;
}
}

工具类如下:

public class GenericTokenParser {

  private final String openToken; //开始标记
private final String closeToken; //结束标记
private final TokenHandler handler; //标记处理器 public GenericTokenParser(String openToken, String closeToken, TokenHandler handler) {
this.openToken = openToken;
this.closeToken = closeToken;
this.handler = handler;
} /**
* 解析${}和#{}
* @param text
* @return
* 该方法主要实现了配置文件、脚本等片段中占位符的解析、处理工作,并返回最终需要的数据。
* 其中,解析工作由该方法完成,处理工作是由处理器handler的handleToken()方法来实现
*/
public String parse(String text) {
// 验证参数问题,如果是null,就返回空字符串。
if (text == null || text.isEmpty()) {
return "";
} // 下面继续验证是否包含开始标签,如果不包含,默认不是占位符,直接原样返回即可,否则继续执行。
int start = text.indexOf(openToken, 0);
if (start == -1) {
return text;
} // 把text转成字符数组src,并且定义默认偏移量offset=0、存储最终需要返回字符串的变量builder,
// text变量中占位符对应的变量名expression。判断start是否大于-1(即text中是否存在openToken),如果存在就执行下面代码
char[] src = text.toCharArray();
int offset = 0;
final StringBuilder builder = new StringBuilder();
StringBuilder expression = null;
while (start > -1) {
// 判断如果开始标记前如果有转义字符,就不作为openToken进行处理,否则继续处理
if (start > 0 && src[start - 1] == '\\') {
builder.append(src, offset, start - offset - 1).append(openToken);
offset = start + openToken.length();
} else {
//重置expression变量,避免空指针或者老数据干扰。
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] == '\\') {//如果结束标记前面有转义字符时
// this close token is escaped. remove the backslash and continue.
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) {
// close token was not found.
builder.append(src, start, src.length - start);
offset = src.length;
} else {
//首先根据参数的key(即expression)进行参数处理,返回?作为占位符
builder.append(handler.handleToken(expression.toString()));
offset = end + closeToken.length();
}
}
start = text.indexOf(openToken, offset);
}
if (offset < src.length) {
builder.append(src, offset, src.length - offset);
}
return builder.toString();
}
}
public class ParameterMapping {

    private String content;

    public ParameterMapping(String content) {
this.content = content;
} public String getContent() {
return content;
} public void setContent(String content) {
this.content = content;
}
}
import java.util.ArrayList;
import java.util.List; public class ParameterMappingTokenHandler implements TokenHandler {
private List<ParameterMapping> parameterMappings = new ArrayList<ParameterMapping>(); // context是参数名称 #{id} #{username} public String handleToken(String content) {
parameterMappings.add(buildParameterMapping(content));
return "?";
} private ParameterMapping buildParameterMapping(String content) {
ParameterMapping parameterMapping = new ParameterMapping(content);
return parameterMapping;
} public List<ParameterMapping> getParameterMappings() {
return parameterMappings;
} public void setParameterMappings(List<ParameterMapping> parameterMappings) {
this.parameterMappings = parameterMappings;
} }
public interface TokenHandler {
String handleToken(String content);
}

10.在FrameMyBatis_test项目的pom文件中引入FrameMyBatis的坐标,在准备好的FrameMyBatis_test下编写测试类FrameTest

import com.zae.dao.UserDao;
import com.zae.entity.User;
import com.zae.io.Resources;
import com.zae.session.SqlSession;
import com.zae.session.SqlSessionFactory;
import com.zae.session.SqlSessionFactoryBuilder;
import org.junit.Before;
import org.junit.Test; import java.io.InputStream;
import java.util.List; public class FrameTest { private SqlSession sqlSession; private UserDao userDao; @Before
public void test1() throws Exception{
//获取字节输入流
InputStream inputStream = Resources.getInputStreamByXml("sqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
sqlSession = sqlSessionFactory.openSqlSession();
userDao = sqlSession.getMapper(UserDao.class);
} /**
* 全查
* @throws Exception
*/
@Test
public void test2()throws Exception{
List<User> all = sqlSession.findAll("com.zae.dao.UserDao.findAll");
for (User user : all) {
System.out.println(user);
}
} /**
* 单条
* @throws Exception
*/
@Test
public void test3() throws Exception{
User user = new User();
user.setId("1");
user.setUsername("sss");
User one = sqlSession.findOne("user.findOne", user);
System.out.println(one);
} /**
* 代理对象查询
* @throws Exception
*/
@Test
public void test4() throws Exception{
List<User> userList = userDao.findAll(null);
for (User user : userList) {
System.out.println(user);
}
}
}

我是帝莘,期待与你的技术交流和思想碰撞。

闭关修炼180天--手写持久层框架(mybatis简易版)的更多相关文章

  1. 闭关修炼180天--手写IOC和AOP(xml篇)

    闭关修炼180天--手写IOC和AOP(xml篇) 帝莘 首先先分享一波思维导图,涵盖了一些Spring的知识点,当然这里并不全面,后期我会持续更新知识点. 在手写实现IOC和AOP之前(也就是打造一 ...

  2. Java精进-手写持久层框架

    前言 本文适合有一定java基础的同学,通过自定义持久层框架,可以更加清楚常用的mybatis等开源框架的原理. JDBC操作回顾及问题分析 学习java的同学一定避免不了接触过jdbc,让我们来回顾 ...

  3. 从零搭建springboot服务02-内嵌持久层框架Mybatis

    愿历尽千帆,归来仍是少年 内嵌持久层框架Mybatis 1.所需依赖 <!-- Mysql驱动包 --> <dependency> <groupId>mysql&l ...

  4. Java数据持久层框架 MyBatis之背景知识三

    摘录自:http://www.cnblogs.com/lcngu/p/5437281.html 对于MyBatis的学习而言,最好去MyBatis的官方文档:http://www.mybatis.or ...

  5. 开源顶级持久层框架——mybatis(ibatis)——day02

    mybatis第二天    高级映射 查询缓存 和spring整合          课程复习:         mybatis是什么?         mybatis是一个持久层框架,mybatis ...

  6. Java持久层框架Mybatis入门

    MyBatis是什么 MyBatis是Java的持久层框架,GitHub的star数高达15.8k,是Java技术栈中最热门的ORM框架之一.它支持自定义SQL.存储过程以及高级映射,可以通过XML或 ...

  7. java持久层框架mybatis如何防止sql注入

    看到一篇很好的文章:http://www.jfox.info/ava-persistence-framework-mybatis-how-to-prevent-sql-injection sql注入大 ...

  8. Java数据持久层框架 MyBatis之API学习一(简介)

    对于MyBatis的学习而言,最好去MyBatis的官方文档:http://www.mybatis.org/mybatis-3/zh/index.html 对于语言的学习而言,马上上手去编程,多多练习 ...

  9. Java数据持久层框架 MyBatis之背景知识二

    对于MyBatis的学习而言,最好去MyBatis的官方文档:http://www.mybatis.org/mybatis-3/zh/index.html 对于语言的学习而言,马上上手去编程,多多练习 ...

随机推荐

  1. websocket服务端开发

    基于http请求以拉的方式去做服务器的推送,无论是实时性和有效字节都是差强人意的效果. 公司的im系统在与客户端的交互上实际上借助了websocket来实现服务器与客户端的事实消息推送,今天就来简单了 ...

  2. 【mq读书笔记】如何保证三个消息文件的最终一致性。

    考虑转发任务未成功执行,此时消息服务器Broker宕机,导致commitlog,consumeQueue,IndexFile文件数据不一致. commitlog,consumeQueue遍历每一条消息 ...

  3. Image Inpainting with Learnable Bidirectional Attention Maps

    Image Inpainting with Learnable Bidirectional Attention Maps pytorch 引言 部分卷积(PConv)的缺陷: 1 将含有1个有效值像素 ...

  4. modelviewset与apiview

    modelviewset(认证,权限,限流,序列化,分页,过滤,排序) 1.DRF初始化 1.认证(让用户登录) 2.权限(根据不同用户角色,可以操作不同的表) 3.限流(限制接口访问速度) 4.序列 ...

  5. 一道百度java面试题的多种解法

    下面是我在2018年10月11日二面百度的时候的一个问题: java程序,主进程需要等待多个子进程结束之后再执行后续的代码,有哪些方案可以实现? 这个需求其实我们在工作中经常会用到,比如用户下单一个产 ...

  6. day101:MoFang:模型构造器ModelSchema&注册功能之手机号唯一验证/保存用户注册信息/发送短信验证码

    目录 1.模型构造器:ModelSchema 1.SQLAlchemySchema 2.SQLAlchemyAutoSchema 2.注册功能基本实现 1.关于手机号码的唯一性验证 2.保存用户注册信 ...

  7. DotNetty关键概念及简单示例(基于NET5)

    DotNetty关键概念及简单示例(基于NET5) 目录 DotNetty关键概念及简单示例(基于NET5) 1.DotNetty 设计的关键 1.1 核心组件 1.1.1 Channel 1.1.2 ...

  8. Mongo管理

    MongoDB存储引擎 一. WiredTiger引擎 1.  MongoDB3.2版本以上,设置为存储引擎. 2. 基于文档级别的并发控制功能(锁机制) (1).    锁级别:文档级别 (2). ...

  9. (四)CPU主频与”性能“

    一.什么是性能 CPU的性能就是就是时间的倒数,简单来说:耗时越少,性能越好,主要包含下面两个指标: 响应时间:程序执行耗时 吞吐率:单位时间处理数据或执行程序的量 缩短响应时间,一定时间内可以执行更 ...

  10. 转:为什么浏览器的user-agent字符串以'Mozilla'开头呢?

    本文转自:https://blog.csdn.net/S_gy_Zetrov/article/details/79463093 感谢sgyzetrov翻译 如果熟悉元素审查的童鞋,很多都会发现requ ...