闭关修炼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. C语言讲义——字符串

    字符数组 C语言字符串就是字符数组. 单写字符,用单引号.如:'A'. 字符串用双引号.如:"A"."ABC". #include <stdio.h> ...

  2. 项目、地铁/公交、游戏签到、项目上线后发现新bug该怎么处理

    项目:1.提前分配好业务(每个人该干什么 )2.提前召开会议3.提前挑好人4.准备项目思维导图5.提前审阅项目6.为确保项目按期交付 把控好时间7.给员工提前打好招呼 (提醒加班)8.建立好安全机制9 ...

  3. CentOS下搭建文件共享服务

    nfs部署以及优化 Server端配置 安装rpm服务包 yum install -y nfs-utils 创建数据挂载点 mkdir -p /data 编辑exports文件 vi /etc/exp ...

  4. 第一次UML作业

    这个作业属于哪个课程 https://edu.cnblogs.com/campus/fzzcxy/2018SE2/ 这个作业要求在哪里 https://edu.cnblogs.com/campus/f ...

  5. Python中sorted(iterable, *, key=None, reverse=False)函数参数定义中的独立星号(*)的含义

    老猿在 <Python中函数的参数带星号是什么意思?>中介绍了Python函数中参数带星号的含义,而在实际使用和Python的标准文档中,会看到某写函数(如sorted(iterable, ...

  6. PyQt(Python+Qt)学习随笔:QAbstractItemView的editTriggers属性以及平台编辑键(platform edit key )

    老猿Python博文目录 老猿Python博客地址 editTriggers属性 editTriggers属性用于确认哪些用户操作行为会触发ItemView中的数据项进入编辑模式. 此属性是由枚举类E ...

  7. 《Machine Learning in Action》—— Taoye给你讲讲Logistic回归是咋回事

    在手撕机器学习系列文章的上一篇,我们详细讲解了线性回归的问题,并且最后通过梯度下降算法拟合了一条直线,从而使得这条直线尽可能的切合数据样本集,已到达模型损失值最小的目的. 在本篇文章中,我们主要是手撕 ...

  8. bugkuctf 这 是 一 个 神 奇 的 登 录 界 面

    首先结合源码可以看出这是一道sql注入题. 然后开始萌新的日常操作,尝试单引号闭合,可是并没有用,而且因为单引号注入的题太多,导致并没有立刻开始尝试双引号,之后想起双引号(对,双引号木得牌面)得到如下 ...

  9. 总结下flask中的宏、Jinjia2语法

    这几天学的东西比较多,时间又有点不够用,趁着快吃饭了,赶紧总结总结. 00x1 宏: 如果学过C语言的童鞋,可能知道宏在C语言里面是一个定义一个固定参数的变量.在flask里面,宏是相当于一个函数的作 ...

  10. go学习第四天

    昨天通宵加班,暂停了一天学习,今天再偷懒下,学习半个小时