日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

mybatis-启动源码分析

發布時間:2023/12/3 编程问答 36 豆豆
生活随笔 收集整理的這篇文章主要介紹了 mybatis-启动源码分析 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

【1】測試用例

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><!-- 1)properteis1.1、mybatis 可以使用properties 標簽來引入外部properties配置文件的內容;1.2、resource 引入類路徑下 的資源;1.3、url 是引入網絡路徑或磁盤路徑下的資源;--><properties resource="dbconfig.properties"></properties><!-- 2)settings 包含很多重要的設置項2.1、setting 用來設置每一個設置項:name: 設置項名稱, value:設置項取值;--><!-- 開啟駝峰命名規則,數據庫表字段 a_b 可以解析為 類成員變量 aB --><!-- 顯示指定每個需要更改的配置項,即使有默認值,防止版本更新帶來的問題 --><settings><!-- 開啟駝峰命名法 --><setting name="mapUnderscoreToCamelCase" value="false"/><!-- 懶加載 --><setting name="lazyLoadingEnabled" value="true"/><!-- 侵入型懶加載 --><setting name="aggressiveLazyLoading" value="false"/></settings><!-- 3)別名處理器:可以為java類型起別名(注意:別名不區分大小寫)3.1) typeAlias為某一個java類型取別名, type:指定要起別名的類型全類名,默認別名就是類名小寫,employeealias: 指定新的 別名;3.3)--><typeAliases><!-- 3.1) typeAlias為某一個java類型取別名, type:指定要起別名的類型全類名,默認別名就是類名小寫,employeealias: 指定新的 別名;<typeAlias type="com.swjtu.mybatis.bean.Employee" alias="emp"/>--><!-- 3.2)package: 為某個包下的所有類批量起別名:name: 指定包名,為當前包以及所有的子包的每個類都起一個默認別名(類名小寫);--><!-- 3.3) 批量起別名的情況下,可以使用注解 Alias()為某個類型指定新的別名;存在 a 包 和 a.b 包下都有 Employee類的情況;--><package name="com.swjtu.mybatis.bean"/></typeAliases><!-- 4、environments: 環境們, mybatis可以配置多種環境, default指定使用某種環境。可以達到快速切換環境。4.1)environment:配置一個具體的環境信息;必須有兩個標簽:id代表當前環境的唯一標識4.1.1) transactionManager 必須有, 事務管理器,type 事務管理器類型,type="[JDBC|MANAGED]" 自定義事務管理器:實現 TransactionFactory 接口, type指定全類名 Configuration類中定義了別名: org.apache.ibatis.session.ConfigurationtypeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);4.1.2) dataSource 數據源:type:數據源類型 type="[UNPOOLED|POOLED|JNDI]"typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);自定義數據源: 實現 DataSourceFactory 接口 , type 為自定義數據源的全類名 --> <environments default="development"><!-- 測試環境 --><environment id="test"><transactionManager type="JDBC"/><dataSource type="POOLED"><property name="driver" value="${jdbc.driver}"/><property name="url" value="${jdbc.url}"/><property name="username" value="${jdbc.username}"/><property name="password" value="${jdbc.password}"/></dataSource></environment><!-- 開發環境 --><environment id="development"><transactionManager type="JDBC"/><dataSource type="POOLED"><property name="driver" value="${jdbc.driver}"/><property name="url" value="${jdbc.url}"/><property name="username" value="${jdbc.username}"/><property name="password" value="${jdbc.password}"/></dataSource></environment></environments><!-- 5、databaseIdProvider :支持多數據庫廠商的;type=DB_VENDOR: VendorDatabaseIdProvider作用就是得到數據庫廠商標識(由數據庫驅動自己附帶的 getDatabaseProductName),mybatis根據該標識執行不同的sql;并在 sql 標簽如<select> 添加屬性 databaseId 屬性--> <databaseIdProvider type="DB_VENDOR"><!-- 為不同數據庫廠商起別名 --><property name="Mysql" value="mysql"/><property name="Oracle" value="oracle"/><property name="SQL Server" value="sqlserver"/></databaseIdProvider> <!-- 將我們寫好的sql 映射文件 EmployeeMapper.xml 一定要注冊到全局配置文件 mybatis-config.xml 中;6、mappers:將sql映射注冊到全局配置中;--> <mappers><!-- mapper標簽: 注冊一個sql映射注冊配置文件的兩種方法:方法1:resource: 引用類路徑下的sql 映射文件;方法2:url: 引用網絡路徑或磁盤路徑下的 sql 映射文件;注冊接口的方法:方法1 class: 直接引用(注冊)接口, 寫接口的全類名;補充1: 有sql映射文件,映射文件名必須和接口同名,并且仿造同一個目錄下;補充2: 沒有sql映射文件, 所有sql 利用注解修飾接口上;補充3: 不推薦使用注解,而使用sql映射文件的方式來 進行sql 映射。--><mapper resource="com\swjtu\mybatis\dao\EmployeeMapper.xml"/> <!-- 批量注冊: 前提是要把 Mapper類文件和Mapper sql 映射文件放在同目錄下荔枝:<package name="com.swjtu.mybatis.dao"/> 可以吧 該包下的所有mapper 映射文件注冊到全局配置環境中;--><!-- <mapper class="com.swjtu.mybatis.dao.cwn.MYCwnOprDAO"/> --></mappers></configuration>

執行sql;

public static void main1() {/*1. 獲取 SqlSessionFactory 對象*/SqlSessionFactory sqlSessionFactory = MyBatisUtils.getSqlSessionFactory();/*2.獲取SqlSession對象, 不會自動提交數據*/SqlSession session = sqlSessionFactory.openSession();try { String flag = "0911A"; List<Map<String, Object>> list = new ArrayList<>();for (int i = 0; i < 5; i++) { String indexFormat = String.format("%8d", i);Map<String, Object> map = new HashMap<>(); map.put("RCRD_ID", flag + indexFormat); map.put("CUST_NUM", "CUST_NUM" +flag+ indexFormat);map.put("CUST_NAME", "CUST_NAME" +flag+ indexFormat);map.put("WARN_TIMES", i); list.add(map); } // mapper.insert2CustWarn(list);session.insert("com.swjtu.mybatis.dao.EmployeeMapper.insert2CustWarn", list); session.commit();} finally { session.close();}System.out.println("bingo!");} mapper文件中的 dml 元素 <insert id="insert2CustWarn">INSERT INTO my_cust_warn_tbl(rcrd_id, cust_num, cust_name , warn_times) VALUES <foreach collection="list" item="item" separator=",">(#{item.RCRD_ID}, #{item.CUST_NUM}, #{item.CUST_NAME}, #{item.WARN_TIMES})</foreach></insert>

MyBatisUtils.getSqlSessionFactory();

/*** 獲取SqlSessionFactory * @return*/ public final static SqlSessionFactory getSqlSessionFactory() {String resource = "mybatis-config.xml";InputStream inputStream;try { inputStream = Resources.getResourceAsStream(resource);SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);return sqlSessionFactory;} catch (IOException e) {e.printStackTrace();}return null; }


【2】創建 SqlSessionFactory 對象

SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

實際上返回的是? DefaultSqlSessionFactory;

// SqlSessionFactoryBuilder 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);}

?而 build(Configuration config) 中的 config 是從mybatis-config.xml文件解析而來的;


【2.1】XMLConfigBuilder.parse() 產生Configuration

parser.parse() 方法的邏輯如下(XMLConfigBuilder構造器中新建了 Configuration對象):

// SqlSessionFactoryBuilder private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {super(new Configuration());// 新建了一個 configuration對象ErrorContext.instance().resource("SQL Mapper Configuration");this.configuration.setVariables(props);this.parsed = false;this.environment = environment;this.parser = parser;}public Configuration parse() {if (parsed) {throw new BuilderException("Each XMLConfigBuilder can only be used once.");}parsed = true;parseConfiguration(parser.evalNode("/configuration"));// 解析xml節點return configuration;}private void parseConfiguration(XNode root) {// 解析xml節點try {Properties settings = settingsAsPropertiess(root.evalNode("settings"));//issue #117 read properties firstpropertiesElement(root.evalNode("properties"));loadCustomVfs(settings);typeAliasesElement(root.evalNode("typeAliases"));pluginElement(root.evalNode("plugins"));objectFactoryElement(root.evalNode("objectFactory"));objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));reflectorFactoryElement(root.evalNode("reflectorFactory"));settingsElement(settings);// read it after objectFactory and objectWrapperFactory issue #631environmentsElement(root.evalNode("environments"));databaseIdProviderElement(root.evalNode("databaseIdProvider"));typeHandlerElement(root.evalNode("typeHandlers"));mapperElement(root.evalNode("mappers"));} catch (Exception e) {throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);}}

我們跟蹤了 mapperElement(..)? 方法,它是解析 mybatis-config.xml 文件中在 mapper標簽中配置的resource屬性指定的sql mapper文件,比如

<mappers> <mapper resource="com\swjtu\mybatis\dao\EmployeeMapper.xml"/> <mapper class="com.swjtu.mybatis.dao.EmployeeMapperAnnotation"/> </mappers>

?其中 root.evalNode("mappers") 獲取的值就是 mapper 數組;?

private void mapperElement(XNode parent) throws Exception {if (parent != null) {for (XNode child : parent.getChildren()) {if ("package".equals(child.getName())) {String mapperPackage = child.getStringAttribute("name");configuration.addMappers(mapperPackage);} else {String resource = child.getStringAttribute("resource");String url = child.getStringAttribute("url");String mapperClass = child.getStringAttribute("class");if (resource != null && url == null && mapperClass == null) {ErrorContext.instance().resource(resource);InputStream inputStream = Resources.getResourceAsStream(resource);XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());mapperParser.parse();} else if (resource == null && url != null && mapperClass == null) {ErrorContext.instance().resource(url);InputStream inputStream = Resources.getUrlAsStream(url);XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());mapperParser.parse();} else if (resource == null && url == null && mapperClass != null) {Class<?> mapperInterface = Resources.classForName(mapperClass);configuration.addMapper(mapperInterface);} else {throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");}}}}}

Xnode parent 長這樣(就是 mybatis-config.xml 中 mappers元素內容):

以上代碼,我們發現,解析 mapper有三種方式;

  • 1.通過resource 配置;
  • 2.通過url;
  • 3.通過mapper class;

下面,我們以resource 為例來講解;

if (resource != null && url == null && mapperClass == null) {ErrorContext.instance().resource(resource);InputStream inputStream = Resources.getResourceAsStream(resource);XMLMapperBuilder mapperParser = new XMLMapperBuilder (inputStream, configuration, resource, configuration.getSqlFragments());mapperParser.parse(); }

以上代碼發現,每一個mapper文件,都會創建一個 XMLMapperBuilder 對象來解析它

String resource=“com\swjtu\mybatis\dao\EmployeeMapper.xml”;

XMLMapperBuilder構造器傳入了4個參數

  • ?mapper文件的輸入流(com\swjtu\mybatis\dao\EmployeeMapper.xml文件流);
  • mybatis配置對象Configuration;
  • resource(sql文件名稱com\swjtu\mybatis\dao\EmployeeMapper.xml);
  • sqlFragments-sql片段為空;
  • XMLMapperBuilder構造器;該構造器新建了 MapperBuilderAssistant對象(解析mapper文件的助手)

    public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {this(new XPathParser(inputStream, true, configuration.getVariables(), new XMLMapperEntityResolver()),configuration, resource, sqlFragments);}private XMLMapperBuilder(XPathParser parser, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {super(configuration);this.builderAssistant = new MapperBuilderAssistant(configuration, resource);this.parser = parser;this.sqlFragments = sqlFragments;this.resource = resource;}

    MapperBuilderAssistant() 長這個樣子;


    ?【2.2】XmlMapperBuilder().parse()? 解析mappers下的每個mapper文件內容

    接著,進入? XmlMapperBuilder().parse() 方法,解析mappers下的mapper元素的內容,mapper元素如 ??? <mapper resource="com\swjtu\mybatis\dao\EmployeeMapper.xml"/>??? ;如下:

    代碼步驟:

    • step1)解析mapper文件下 mapper元素內容(主要是 dml標簽元素內容);
    • step2)把mapper文件名添加到 configuration,以避免二次解析;
    • step3)構建命名空間;

    【2.2.1】 XmlMapperBuilder.configurationElement(parser.evalNode("/mapper"))如下:?

    其中 parser.evalNode("/mapper")或context就是 文件 com\swjtu\mybatis\dao\EmployeeMapper.xml? 的內容,如下:

    代碼步驟:

    • step1)設置命名空間;
    • step2)解析緩存;
    • step3)解析 parameterMap 標簽內容;
    • step4)解析 resultMap 標簽內容;
    • step5)解析 sql 標簽內容;
    • step6) 解析 sql 的dml 標簽內容(select或insert或update 或 delete);

    最后一步, buildStatementFromContext(context.evalNodes("select|insert|update|delete")); 就是真正解析 sql的 dml語句的標簽了,即解析出sql文本并加載到內存中;?

    List<XNode>? list 包含了多個 dml標簽的內容數組;

    接著 buildStatementFromContext(List<XNode> list, String requiredDatabaseId) 遍歷list中的每個dml 標簽元素;? 如下:

    private void buildStatementFromContext(List<XNode> list) {if (configuration.getDatabaseId() != null) {buildStatementFromContext(list, configuration.getDatabaseId());}buildStatementFromContext(list, null);}private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {for (XNode context : list) {final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);try {statementParser.parseStatementNode();} catch (IncompleteElementException e) {configuration.addIncompleteStatement(statementParser);}}}

    以上代碼,每個 dml標簽元素 就會創建一個 XMLStatementBuilder 對象

    statementParser.parseStatementNode();?

    就用于解析 context 文本(dml標簽元素內容); 如下:

    public void parseStatementNode() {String id = context.getStringAttribute("id");String databaseId = context.getStringAttribute("databaseId");if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {return;}Integer fetchSize = context.getIntAttribute("fetchSize");Integer timeout = context.getIntAttribute("timeout");String parameterMap = context.getStringAttribute("parameterMap");String parameterType = context.getStringAttribute("parameterType");Class<?> parameterTypeClass = resolveClass(parameterType);String resultMap = context.getStringAttribute("resultMap");String resultType = context.getStringAttribute("resultType");String lang = context.getStringAttribute("lang");LanguageDriver langDriver = getLanguageDriver(lang);Class<?> resultTypeClass = resolveClass(resultType);String resultSetType = context.getStringAttribute("resultSetType");StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);String nodeName = context.getNode().getNodeName();SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));boolean isSelect = sqlCommandType == SqlCommandType.SELECT;boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);boolean useCache = context.getBooleanAttribute("useCache", isSelect);boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);// Include Fragments before parsingXMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);includeParser.applyIncludes(context.getNode());// Parse selectKey after includes and remove them.processSelectKeyNodes(id, parameterTypeClass, langDriver);// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);String resultSets = context.getStringAttribute("resultSets");String keyProperty = context.getStringAttribute("keyProperty");String keyColumn = context.getStringAttribute("keyColumn");KeyGenerator keyGenerator;String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);if (configuration.hasKeyGenerator(keyStatementId)) {keyGenerator = configuration.getKeyGenerator(keyStatementId);} else {keyGenerator = context.getBooleanAttribute("useGeneratedKeys",configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))? new Jdbc3KeyGenerator() : new NoKeyGenerator();}builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,resultSetTypeEnum, flushCache, useCache, resultOrdered, keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);}

    以上代碼的步驟如下:

    step1)解析 dml元素中各種屬性,包括 parameterMap,parameterType,resultMap,resultType等;

    Integer fetchSize = context.getIntAttribute("fetchSize");Integer timeout = context.getIntAttribute("timeout");String parameterMap = context.getStringAttribute("parameterMap");String parameterType = context.getStringAttribute("parameterType");Class<?> parameterTypeClass = resolveClass(parameterType);String resultMap = context.getStringAttribute("resultMap");String resultType = context.getStringAttribute("resultType");

    resultType 為map;因為 dml標簽元素如下:

    <select id="getEmpByLastNameLikeReturnMap" resultType="map">SELECT id AS ID, last_name AS LASTNAME, email AS EMAIL, gender AS GENDERFROM emp_tbl WHERE last_name like #{lastName}</select>

    step2)獲取語言驅動器, LanguageDriver langDriver = getLanguageDriver(lang);

    step3)??? Class<?> resultTypeClass = resolveClass(resultType);:返回 resultType的class類型;如 resultType為map,則返回 Map.class ;

    protected Class<?> resolveClass(String alias) { // 以alias為map為例if (alias == null) {return null;}try {return resolveAlias(alias);} catch (Exception e) {throw new BuilderException("Error resolving class. Cause: " + e, e);}} // BaseBuilder.java protected Class<?> resolveAlias(String alias) {return typeAliasRegistry.resolveAlias(alias);}public <T> Class<T> resolveAlias(String string) {try {if (string == null) {return null;}// issue #748String key = string.toLowerCase(Locale.ENGLISH);Class<T> value;if (TYPE_ALIASES.containsKey(key)) {value = (Class<T>) TYPE_ALIASES.get(key);} else {value = (Class<T>) Resources.classForName(string);}return value;} catch (ClassNotFoundException e) {throw new TypeException("Could not resolve type alias '" + string + "'. Cause: " + e, e);}}

    typeAliasRegistry 為類型別名注冊器,如下:

    public TypeAliasRegistry() {registerAlias("string", String.class);registerAlias("byte", Byte.class);registerAlias("long", Long.class);registerAlias("short", Short.class);registerAlias("int", Integer.class);registerAlias("integer", Integer.class);registerAlias("double", Double.class);registerAlias("float", Float.class);registerAlias("boolean", Boolean.class);registerAlias("byte[]", Byte[].class);registerAlias("long[]", Long[].class);registerAlias("short[]", Short[].class);registerAlias("int[]", Integer[].class);registerAlias("integer[]", Integer[].class);registerAlias("double[]", Double[].class);registerAlias("float[]", Float[].class);registerAlias("boolean[]", Boolean[].class);registerAlias("_byte", byte.class);registerAlias("_long", long.class);registerAlias("_short", short.class);registerAlias("_int", int.class);registerAlias("_integer", int.class);registerAlias("_double", double.class);registerAlias("_float", float.class);registerAlias("_boolean", boolean.class);registerAlias("_byte[]", byte[].class);registerAlias("_long[]", long[].class);registerAlias("_short[]", short[].class);registerAlias("_int[]", int[].class);registerAlias("_integer[]", int[].class);registerAlias("_double[]", double[].class);registerAlias("_float[]", float[].class);registerAlias("_boolean[]", boolean[].class);registerAlias("date", Date.class);registerAlias("decimal", BigDecimal.class);registerAlias("bigdecimal", BigDecimal.class);registerAlias("biginteger", BigInteger.class);registerAlias("object", Object.class);registerAlias("date[]", Date[].class);registerAlias("decimal[]", BigDecimal[].class);registerAlias("bigdecimal[]", BigDecimal[].class);registerAlias("biginteger[]", BigInteger[].class);registerAlias("object[]", Object[].class);registerAlias("map", Map.class);registerAlias("hashmap", HashMap.class);registerAlias("list", List.class);registerAlias("arraylist", ArrayList.class);registerAlias("collection", Collection.class);registerAlias("iterator", Iterator.class);registerAlias("ResultSet", ResultSet.class);}

    step4)獲取sql語句類型, PREPARED ;

    step5)獲取sql命令類型 SqlCommandType -SELECT

    String nodeName = context.getNode().getNodeName(); // nodeName為selectSqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));public enum SqlCommandType {UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH; }

    step6)解析是否需要使用緩存

    boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);//false boolean useCache = context.getBooleanAttribute("useCache", isSelect);//true boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);//false

    step7)解析dml元素中 include元素內容(比如 include sql);

    XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);includeParser.applyIncludes(context.getNode());

    public void applyIncludes(Node source) {Properties variablesContext = new Properties();Properties configurationVariables = configuration.getVariables();if (configurationVariables != null) {variablesContext.putAll(configurationVariables);}applyIncludes(source, variablesContext);}

    ?step8)解析 selectKey;sql中沒有,不用考慮

    processSelectKeyNodes(id, parameterTypeClass, langDriver);

    ?step9)創建sql 映射對象

    SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);

    langDriver 指向了 org.apache.ibatis.scripting.xmltags.XMLLanguageDriver

    public class XMLLanguageDriver implements LanguageDriver {@Overridepublic ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {return new DefaultParameterHandler(mappedStatement, parameterObject, boundSql);}@Overridepublic SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);return builder.parseScriptNode();}@Overridepublic SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType) {// issue #3if (script.startsWith("<script>")) {XPathParser parser = new XPathParser(script, false, configuration.getVariables(), new XMLMapperEntityResolver());return createSqlSource(configuration, parser.evalNode("/script"), parameterType);} else {// issue #127script = PropertyParser.parse(script, configuration.getVariables());TextSqlNode textSqlNode = new TextSqlNode(script);if (textSqlNode.isDynamic()) {return new DynamicSqlSource(configuration, textSqlNode);} else {return new RawSqlSource(configuration, script, parameterType);}}}}

    builder.parseScriptNode()? 調用的是 XMLScriptBuilder.parseScriptNode() ; 解析動態sql (用${} 包裹的sql );

    public SqlSource parseScriptNode() {List<SqlNode> contents = parseDynamicTags(context);MixedSqlNode rootSqlNode = new MixedSqlNode(contents);SqlSource sqlSource = null;if (isDynamic) {sqlSource = new DynamicSqlSource(configuration, rootSqlNode);} else {sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);}return sqlSource;}List<SqlNode> parseDynamicTags(XNode node) {List<SqlNode> contents = new ArrayList<SqlNode>();NodeList children = node.getNode().getChildNodes();for (int i = 0; i < children.getLength(); i++) {XNode child = node.newXNode(children.item(i));if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {String data = child.getStringBody("");TextSqlNode textSqlNode = new TextSqlNode(data);if (textSqlNode.isDynamic()) {contents.add(textSqlNode);isDynamic = true;} else {contents.add(new StaticTextSqlNode(data));}} else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628String nodeName = child.getNode().getNodeName();NodeHandler handler = nodeHandlers(nodeName);if (handler == null) {throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");}handler.handleNode(child, contents);isDynamic = true;}}return contents;}

    因為我們不是動態sql,所以返回的是 RawSqlSource

    sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);

    step10)解析 resultSets, keyProperty, keyColumn 屬性等 (也不用太關心);?

    String resultSets = context.getStringAttribute("resultSets");String keyProperty = context.getStringAttribute("keyProperty");String keyColumn = context.getStringAttribute("keyColumn");KeyGenerator keyGenerator;String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);if (configuration.hasKeyGenerator(keyStatementId)) {keyGenerator = configuration.getKeyGenerator(keyStatementId);} else {keyGenerator = context.getBooleanAttribute("useGeneratedKeys",configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))? new Jdbc3KeyGenerator() : new NoKeyGenerator();}

    step11)MapperBuilderAssistant.addMappedStatement() 把解析得到的sql映射對象添加到configuration;

    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,resultSetTypeEnum, flushCache, useCache, resultOrdered, keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);

    參數列表如下:

    • 1.id,getEmpByLastNameLikeReturnMap;
    • 2.sqlSource,

    ?parameterMappins 明顯是 參數映射;

    • 3.statementType , PREPARED;
    • 4.sqlCommandType,SELECT;
    • 5.fetchSize,null;
    • 6.timeout, null;
    • 7.parameterMap,null;
    • 8.parameterTypeClass,null;
    • 9.resultMap,null;
    • 10.resultTypeClass, interface java.util.Map;
    • 11.resultSetTypeEnum,null;
    • 12.flushCache,false;
    • 13.useCache,true;
    • 14.resultOrdered,false;
    • 15.keyGenerator,org.apache.ibatis.executor.keygen.NoKeyGenerator@5ef8df1e;
    • 16.keyProperty,null;
    • 17.keyColumn,null;
    • 18.databaseId,null;
    • 19.langDriver, org.apache.ibatis.scripting.xmltags.XMLLanguageDriver@32057e6;
    • 20.resultSets, null;

    MapperBuilderAssistant.addMappedStatement(...)? 源碼如下:

    public MappedStatement addMappedStatement(...) {if (unresolvedCacheRef) {throw new IncompleteElementException("Cache-ref not yet resolved");}id = applyCurrentNamespace(id, false);boolean isSelect = sqlCommandType == SqlCommandType.SELECT;MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType).resource(resource).fetchSize(fetchSize).timeout(timeout).statementType(statementType).keyGenerator(keyGenerator).keyProperty(keyProperty).keyColumn(keyColumn).databaseId(databaseId).lang(lang).resultOrdered(resultOrdered).resultSets(resultSets).resultMaps(getStatementResultMaps(resultMap, resultType, id)).resultSetType(resultSetType).flushCacheRequired(valueOrDefault(flushCache, !isSelect)).useCache(valueOrDefault(useCache, isSelect)).cache(currentCache);ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);if (statementParameterMap != null) {statementBuilder.parameterMap(statementParameterMap);}MappedStatement statement = statementBuilder.build();configuration.addMappedStatement(statement);return statement;}

    id = applyCurrentNamespace(id, false); 這一步,給id添加命名空間前綴,

    • 執行前;id為? getEmpByLastNameLikeReturnMap ;
    • 執行后,id為 com.swjtu.mybatis.dao.EmployeeMapper.getEmpByLastNameLikeReturnMap;

    解析每個dml元素時,都要創建一個? MappedStatement.Builder(...) 用于創建 MappedStatement( sql映射信息)

    MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)

    MappedStatement statement = statementBuilder.build(); 源碼如下:

    public MappedStatement build() {assert mappedStatement.configuration != null;assert mappedStatement.id != null;assert mappedStatement.sqlSource != null;assert mappedStatement.lang != null;mappedStatement.resultMaps = Collections.unmodifiableList(mappedStatement.resultMaps);return mappedStatement;}


    補充:MappedStatement對象

    MappedStatement 實際是封裝了 sql映射信息的對象 ,sql映射是在 MapperBuilderAssistant.addMappedStatement(...) 方法里面構建;結構如下:

    每條sql都是一個 MappedStatement對象,解析完成后,將其添加到 configuration對象中;

    ?configuration 就是根據mybatis-config.xml 文件解析出的 Configuration 對象;長這個樣子:


    【2.2.2】 configuration.addLoadedResource(resource);

    把已經解析的resource 文件名添加到 configuration,避免二次解析;


    【2.2.3】XMLMapperBuilder.bindMapperForNamespace() 創建命名空間

    private void bindMapperForNamespace() {String namespace = builderAssistant.getCurrentNamespace();if (namespace != null) {Class<?> boundType = null;try {boundType = Resources.classForName(namespace);} catch (ClassNotFoundException e) {//ignore, bound type is not required}if (boundType != null) {if (!configuration.hasMapper(boundType)) {// Spring may not know the real resource name so we set a flag// to prevent loading again this resource from the mapper interface// look at MapperAnnotationBuilder#loadXmlResourceconfiguration.addLoadedResource("namespace:" + namespace);configuration.addMapper(boundType);}}}}public <T> void addMapper(Class<T> type) {mapperRegistry.addMapper(type);}

    很顯然,當 有mapperclass的時候,才會設置 boundType;如果沒有mapper class也沒有關系;因為catch會忽略異常;


    【2.2.4】解析其他

    parsePendingResultMaps();parsePendingChacheRefs();parsePendingStatements();

    【2.2.5】創建 DefaultSqlSession對象并返回

    解析完成后,生成Configuration對象;


    【3】sqlSessionFactory.openSession()打開會話

    調用的是 DefaultSqlSessionFactory.openSession()

    // DefaultSqlSessionFactory @Overridepublic 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);// 返回的是 DefaultSqlSession 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();}}


    【3.1】DefaultSqlSessionFactory 中有兩類方法包括,

  • 從數據源獲取會話:private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit);
  • 從連接中獲取會話:private SqlSession openSessionFromConnection(ExecutorType execType, Connection connection) ;
  • 兩者不同點在于

    tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);

    final Transaction tx = transactionFactory.newTransaction(connection);

    // 從數據源獲取會話 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();}}// 從數據庫連接獲取會話 private SqlSession openSessionFromConnection(ExecutorType execType, Connection connection) {try {boolean autoCommit;try {autoCommit = connection.getAutoCommit();} catch (SQLException e) {// Failover to true, as most poor drivers// or databases won't support transactionsautoCommit = true;} final Environment environment = configuration.getEnvironment();final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);final Transaction tx = transactionFactory.newTransaction(connection);final Executor executor = configuration.newExecutor(tx, execType);return new DefaultSqlSession(configuration, executor, autoCommit);} catch (Exception e) {throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);} finally {ErrorContext.instance().reset();}}

    獲取會話無論采用 datasource,還是connection方式,其步驟如下:

    • step1)獲取環境對象;
    • step2)獲取事務工廠;
    • step3)創建事務;
    • step4)創建執行器;
    • step5)創建會話 DefaultSqlSession;

    【3.2】事務工廠

    // 從環境對象中獲取事務工廠private TransactionFactory getTransactionFactoryFromEnvironment(Environment environment) {if (environment == null || environment.getTransactionFactory() == null) {return new ManagedTransactionFactory();}return environment.getTransactionFactory();}

    如果配置了環境元素,則返回配置的環境的事務工廠;否則默認返回 ManagedTransactionFactory;

    此外,事務工廠有3個,如下:


    【3.3】創建事務-ManagedTransaction

    // 可管理的事務工廠 public class ManagedTransactionFactory implements TransactionFactory {private boolean closeConnection = true;@Overridepublic void setProperties(Properties props) {if (props != null) {String closeConnectionProperty = props.getProperty("closeConnection");if (closeConnectionProperty != null) {closeConnection = Boolean.valueOf(closeConnectionProperty);}}}@Overridepublic Transaction newTransaction(Connection conn) {return new ManagedTransaction(conn, closeConnection);} // 創建事務對象@Overridepublic Transaction newTransaction(DataSource ds, TransactionIsolationLevel level, boolean autoCommit) {// Silently ignores autocommit and isolation level, as managed transactions are entirely// controlled by an external manager. It's silently ignored so that// code remains portable between managed and unmanaged configurations.return new ManagedTransaction(ds, level, closeConnection);} }

    可管理事務: ManagedTransaction;?

    public class ManagedTransaction implements Transaction {private static final Log log = LogFactory.getLog(ManagedTransaction.class);private DataSource dataSource;private TransactionIsolationLevel level;private Connection connection;private boolean closeConnection;public ManagedTransaction(Connection connection, boolean closeConnection) {this.connection = connection;this.closeConnection = closeConnection;}public ManagedTransaction(DataSource ds, TransactionIsolationLevel level, boolean closeConnection) {this.dataSource = ds;this.level = level;this.closeConnection = closeConnection;}@Overridepublic Connection getConnection() throws SQLException {if (this.connection == null) {openConnection();}return this.connection;}@Overridepublic void commit() throws SQLException {// Does nothing}@Overridepublic void rollback() throws SQLException {// Does nothing}@Overridepublic void close() throws SQLException {if (this.closeConnection && this.connection != null) {if (log.isDebugEnabled()) {log.debug("Closing JDBC Connection [" + this.connection + "]");}this.connection.close();}}protected void openConnection() throws SQLException {if (log.isDebugEnabled()) {log.debug("Opening JDBC Connection");}this.connection = this.dataSource.getConnection();if (this.level != null) {this.connection.setTransactionIsolation(this.level.getLevel());}}}

    ?它的commit, rollback 都是空的;

    它的數據庫連接的獲取有兩種方式,

  • 上游給定,傳入 Connection ;
  • 通過 datasource 數據源獲取connection;

  • 【3.4】創建執行器

    1)ExecutorType 是枚舉類型,默認為SIMPLE ;

    final Executor executor = configuration.newExecutor(tx, execType);// Configuration.newExecutor 方法 public Executor newExecutor(Transaction transaction, ExecutorType executorType) {executorType = executorType == null ? defaultExecutorType : executorType;executorType = executorType == null ? ExecutorType.SIMPLE : executorType;Executor executor;if (ExecutorType.BATCH == executorType) {executor = new BatchExecutor(this, transaction);} else if (ExecutorType.REUSE == executorType) {executor = new ReuseExecutor(this, transaction);} else {executor = new SimpleExecutor(this, transaction);}if (cacheEnabled) {executor = new CachingExecutor(executor);}executor = (Executor) interceptorChain.pluginAll(executor);return executor;}

    如果為SIMPLE,則返回 SimpleExecutor 執行器;? 但 如果啟用緩存,則封裝SimpleExecutor到緩存執行器

    最后把 執行器添加到攔截器列表,攔截器 Interceptor 列表如下,本質上是一個 ArrayList數組;

    // 攔截器 public class InterceptorChain {private final List<Interceptor> interceptors = new ArrayList<Interceptor>();public Object pluginAll(Object target) {for (Interceptor interceptor : interceptors) {target = interceptor.plugin(target);}return target;}public void addInterceptor(Interceptor interceptor) {interceptors.add(interceptor);}public List<Interceptor> getInterceptors() {return Collections.unmodifiableList(interceptors);}}

    其中SimpleExecutor父類Executor的實現類 包括

    2)SimpleExecutor 長這個樣子

    public class SimpleExecutor extends BaseExecutor {public SimpleExecutor(Configuration configuration, Transaction transaction) {super(configuration, transaction);}@Overridepublic int doUpdate(MappedStatement ms, Object parameter) throws SQLException {Statement stmt = null;try {Configuration configuration = ms.getConfiguration();StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);stmt = prepareStatement(handler, ms.getStatementLog());return handler.update(stmt);} finally {closeStatement(stmt);}}@Overridepublic <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {Statement stmt = null;try {Configuration configuration = ms.getConfiguration();StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);stmt = prepareStatement(handler, ms.getStatementLog());return handler.<E>query(stmt, resultHandler);} finally {closeStatement(stmt);}}@Overrideprotected <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql) throws SQLException {Configuration configuration = ms.getConfiguration();StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, null, boundSql);Statement stmt = prepareStatement(handler, ms.getStatementLog());return handler.<E>queryCursor(stmt);}@Overridepublic List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException {return Collections.emptyList();}private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {Statement stmt;Connection connection = getConnection(statementLog);stmt = handler.prepare(connection, transaction.getTimeout());handler.parameterize(stmt);return stmt;}}

    【3.5】 創建會話 DefaultSqlSession

    public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {this.configuration = configuration;this.executor = executor;this.dirty = false;this.autoCommit = autoCommit;}

    【4】session.insert(...)

    【4.1】DefaultSqlSession.insert()?

    session.insert(...); 它實際調用的是 DefaultSqlSession.insert() 方法;

    // DefaultSqlSession @Overridepublic int insert(String statement, Object parameter) {return update(statement, parameter);}@Overridepublic int update(String statement) {return update(statement, null);}@Overridepublic int update(String statement, Object parameter) {try {dirty = true;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();}}

    ?executor.update(ms, warpCollection(parameter)); 中的? executor是 SimpleExecutor;

    我們接著看下 SimpleExecutor-執行器 ;

    // 簡單執行器 public class SimpleExecutor extends BaseExecutor {public SimpleExecutor(Configuration configuration, Transaction transaction) {super(configuration, transaction);}// 執行器基類 public abstract class BaseExecutor implements Executor {private static final Log log = LogFactory.getLog(BaseExecutor.class);protected Transaction transaction;protected Executor wrapper;protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads;protected PerpetualCache localCache;protected PerpetualCache localOutputParameterCache;protected Configuration configuration;protected int queryStack = 0;private boolean closed;protected BaseExecutor(Configuration configuration, Transaction transaction) {this.transaction = transaction;this.deferredLoads = new ConcurrentLinkedQueue<DeferredLoad>();this.localCache = new PerpetualCache("LocalCache");this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache");this.closed = false;this.configuration = configuration;this.wrapper = this;}

    BaseExecutor.update(MappedStatement ms, Object parameter) 如下:

    @Overridepublic int update(MappedStatement ms, Object parameter) throws SQLException {ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());if (closed) {throw new ExecutorException("Executor was closed.");}clearLocalCache();return doUpdate(ms, parameter);}

    SimpleExecutor.doUpdate(...)

    @Overridepublic int doUpdate(MappedStatement ms, Object parameter) throws SQLException {Statement stmt = null;try {Configuration configuration = ms.getConfiguration();StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);stmt = prepareStatement(handler, ms.getStatementLog());return handler.update(stmt);} finally {closeStatement(stmt);}}

    上述是執行sql 語句的地方,依賴的對象包括:

  • configuration:根據mybatis-config.xml 解析得到的配置對象;
  • StatementHandler:語句處理器;
  • PreparedStatement:預編譯語句;
  • Configuration.newStatementHandler(...) 方法如下:

    public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);return statementHandler;}

    參數列表為:

    參數1-executor;

    參數2-mappedStatement;?

    參數3-parameterObject;

    ?

    參數4- parameterObject 為:

    {collection=[{RCRD_ID=0908A 0,CUST_NUM=CUST_NUM 0,WARN_TIMES=0,CUST_NAME=CUST_NAME 0},{RCRD_ID=0908A 1,CUST_NUM=CUST_NUM 1,WARN_TIMES=1,CUST_NAME=CUST_NAME 1},{RCRD_ID=0908A 2,CUST_NUM=CUST_NUM 2,WARN_TIMES=2,CUST_NAME=CUST_NAME 2},...],list=[{RCRD_ID=0908A 0,CUST_NUM=CUST_NUM 0,WARN_TIMES=0,CUST_NAME=CUST_NAME 0},{RCRD_ID=0908A 1,CUST_NUM=CUST_NUM 1,WARN_TIMES=1,CUST_NAME=CUST_NAME 1},{RCRD_ID=0908A 2,CUST_NUM=CUST_NUM 2,WARN_TIMES=2,CUST_NAME=CUST_NAME 2},]}

    參數5-rowBounds?

    ?

    ?參數6-resultHandler 為null;

    參數7-boundSql 為null ;


    SimpleExecutor.newStatementHandler(...) 如下:

    delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);

    ?

    代碼步驟:

    step1)獲取mybatis 配置對象;

    step2)獲取 StatementHandler;

    StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);

    StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);// 新建語句處理器public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);return statementHandler;}

    step3)prepareStatement(StatementHandler handler, Log statementLog) ,把預編譯sql 的參數賦值;

    handler 為 PreparedStatementHandler ;

    • step3.1)stmt = handler.prepare(connection, transaction.getTimeout()); 獲取預編譯語句;
    // RoutingStatementHandler @Overridepublic Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {return delegate.prepare(connection, transactionTimeout);}// BaseStatemetnHandler@Overridepublic Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {ErrorContext.instance().sql(boundSql.getSql());Statement statement = null;try {statement = instantiateStatement(connection);setStatementTimeout(statement, transactionTimeout);setFetchSize(statement);return statement;} catch (SQLException e) {closeStatement(statement);throw e;} catch (Exception e) {closeStatement(statement);throw new ExecutorException("Error preparing statement. Cause: " + e, e);}}
    • step3.2)handler.parameterize(stmt); 對參數進行賦值
    // RoutingStatementHandler@Overridepublic void parameterize(Statement statement) throws SQLException {delegate.parameterize(statement);}// PreparedStatementHandler @Overridepublic void parameterize(Statement statement) throws SQLException {parameterHandler.setParameters((PreparedStatement) statement);}//

    parameterHandler.setParameters(...) ;源碼如下:

    遍歷 sql語句中所有參數,并賦值

    public void setParameters(PreparedStatement ps) {ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();if (parameterMappings != null) {for (int i = 0; i < parameterMappings.size(); i++) {ParameterMapping parameterMapping = parameterMappings.get(i);if (parameterMapping.getMode() != ParameterMode.OUT) {Object value;String propertyName = parameterMapping.getProperty();if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional paramsvalue = boundSql.getAdditionalParameter(propertyName);} else if (parameterObject == null) {value = null;} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {value = parameterObject;} else {MetaObject metaObject = configuration.newMetaObject(parameterObject);value = metaObject.getValue(propertyName);}TypeHandler typeHandler = parameterMapping.getTypeHandler();JdbcType jdbcType = parameterMapping.getJdbcType();if (value == null && jdbcType == null) {jdbcType = configuration.getJdbcTypeForNull();}try {typeHandler.setParameter(ps, i + 1, value, jdbcType);} catch (TypeException e) {throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);} catch (SQLException e) {throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);}}}

    ?parameterMappings 是參數信息,如CUST_NUM ;??

    ?boundsql 是預編譯sql,帶有問號;?

    ?

    ?

    ?設置參數;

    step4)handler.update() 執行具體 sql; ?

    @Overridepublic int update(Statement statement) throws SQLException {return delegate.update(statement);} // delegate 代理為 PreparedStatementHandler

    執行具體sql,如下:

    @Overridepublic int update(Statement statement) throws SQLException {PreparedStatement ps = (PreparedStatement) statement;ps.execute(); // 執行sql int rows = ps.getUpdateCount(); // 獲取更新行數 Object parameterObject = boundSql.getParameterObject();KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);return rows;}

    總結

    以上是生活随笔為你收集整理的mybatis-启动源码分析的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

    成人免费视频在线观看 | 日韩一区二区三区高清免费看看 | 91看片在线 | 一区二区三区在线播放 | 亚洲精品一区二区久 | 亚洲黄色一级大片 | 在线亚洲欧美视频 | 亚洲视频在线免费看 | 婷婷久久综合网 | 中文字幕专区高清在线观看 | 国产亚洲成av片在线观看 | 一区二区三区久久精品 | 九九视频网站 | 看av免费 | 国产日韩亚洲 | 99久国产 | 久青草电影 | 久久久精品国产一区二区电影四季 | 国产情侣一区 | 亚洲另类久久 | 精品国产乱码久久久久久天美 | 在线观看的av网站 | 日韩精品久久久 | 国产精品国产自产拍高清av | 久久久首页| 亚洲欧美久久 | 亚洲国产中文字幕在线视频综合 | 久草在线视频网站 | 婷婷丁香狠狠爱 | 麻豆精品91| 国产网红在线 | 亚洲国产精品推荐 | 97福利视频 | 波多野结衣精品视频 | 成人精品一区二区三区中文字幕 | 日本在线精品视频 | 免费视频91| 97免费视频在线播放 | 狠狠搞,com | 国产一级片免费观看 | 在线观看中文 | 国产手机av | 亚洲一区欧美激情 | 亚洲精品伦理在线 | 精品一区二区免费在线观看 | 天天操天天操天天操天天操 | 伊人网综合在线观看 | 欧美成人亚洲 | 色婷婷国产精品 | 久久久久久免费网 | 不卡视频一区二区三区 | 免费看精品久久片 | 玖玖视频国产 | 99久久日韩精品免费热麻豆美女 | 欧美日韩一二三四区 | 国产专区第一页 | 一区二区三区免费在线 | 高清日韩一区二区 | 久久精品国产一区二区三区 | 99精品在线免费在线观看 | 免费成人av | 欧美日韩在线观看一区 | 91夜夜夜| 亚洲在线高清 | 久久久久区| 久久久夜色 | 91系列在线观看 | wwwwww色 | 日韩欧美视频一区二区 | 97超碰免费在线 | 99久久99| 91精品久久久久久久久 | 久久久久久久久久久影视 | 2019中文字幕第一页 | 国产精品久久久久av免费 | 国产精品久久久久久久久久久免费看 | 日韩av视屏| a在线v | 欧美激情综合五月色丁香 | 亚洲综合在线播放 | 国产精品欧美久久久久三级 | 国产成人精品一区二区三区 | 麻豆视频网址 | 69欧美视频 | 中文字幕在线观看免费高清电影 | 国产在线观看不卡 | 波多野结衣在线中文字幕 | 国产一区不卡在线 | 国产特级毛片aaaaaa毛片 | 99视频免费看| 午夜私人影院久久久久 | 成人黄大片 | 国产视频欧美视频 | 欧美色操 | 欧美日韩另类在线观看 | 中文免费观看 | 久久这里只有精品23 | 91插插插免费视频 | 毛片一级免费一级 | 操操操夜夜操 | 久久女教师 | 亚洲精品色 | 国产资源中文字幕 | 日韩精品欧美一区 | 色午夜影院 | www.神马久久 | 91福利国产在线观看 | 色五丁香 | 欧美一区二区免费在线观看 | 精品久久久久久亚洲综合网 | 国产拍在线 | 天天射天天操天天干 | 欧美一级免费黄色片 | 久九视频 | 国产精品成人一区二区 | 九九视频在线观看视频6 | 成年人在线免费看 | 综合天天网 | 精品一区二区视频 | 亚洲成aⅴ人片久久青草影院 | 午夜精品影院 | 又色又爽又黄高潮的免费视频 | 国产精品久久久久久超碰 | 日韩r级电影在线观看 | 日本视频精品 | 亚州性色| 色噜噜日韩精品欧美一区二区 | 园产精品久久久久久久7电影 | 一区二区三区在线免费播放 | 热久久精品在线 | 人人爱爱| 久久久久国产免费免费 | 99精品视频免费在线观看 | 97国产大学生情侣白嫩酒店 | 香蕉视频久久久 | 欧美一级电影片 | av丝袜天堂 | 中文在线字幕免费观看 | 久久好看免费视频 | 视频国产一区二区三区 | 99草在线视频 | 91丨九色丨首页 | 91成人蝌蚪 | 国产精品一区二区吃奶在线观看 | 中文字幕免费久久 | 亚洲国产三级 | 久操视频在线 | 日本中文一区二区 | 夜夜夜夜夜夜操 | 欧美国产精品一区二区 | 欧美老女人xx | 夜夜操网 | 六月丁香色婷婷 | 国产区高清在线 | 欧美一区二区三区免费看 | 国产一级在线免费观看 | 成人动漫一区二区 | 久久不射影院 | 精品久久片| 99精品欧美一区二区三区 | 久久久久久久久国产 | 婷婷开心久久网 | 中文字幕在线观看免费高清电影 | 亚洲黄色片在线 | 亚洲aⅴ在线观看 | 深爱五月激情五月 | 最新av在线播放 | 人人爽人人爽人人片av免 | 国产亚洲视频在线免费观看 | 一区二区影视 | 97超碰人人爱 | 一区二区欧美激情 | 成人在线视频你懂的 | av在线播放网址 | 正在播放国产一区 | 91精品国产乱码在线观看 | 日本黄色一级电影 | 久久精品视频18 | 欧美精品国产综合久久 | 三级av免费观看 | 精品亚洲视频在线观看 | 激情丁香月 | 在线小视频 | 四虎在线永久免费观看 | 国产亚洲在 | 天天操天天色天天射 | 一区二区三区四区不卡 | 国产精品午夜久久久久久99热 | 国产黄色片免费 | 九九免费在线观看 | 国产老太婆免费交性大片 | 国产精品一区在线观看 | 欧美性大胆 | 美女视频网站久久 | 久久婷亚洲五月一区天天躁 | 色综合久久久 | 99在线精品免费视频九九视 | 久久99深爱久久99精品 | 亚洲精品在线观看网站 | 免费在线观看av片 | 免费在线国产黄色 | 91视频网址入口 | 国产精品资源网 | 超碰激情在线 | 97电影在线 | 在线看v片成人 | 丰满少妇久久久 | 日韩最新av在线 | 欧美日本不卡视频 | 99视频在线免费 | 色诱亚洲精品久久久久久 | 91精品国产自产91精品 | 国产精品毛片一区视频播不卡 | 中文字幕一区二区三区乱码在线 | 国产1区2区3区精品美女 | 天天插日日插 | 欧美一二三视频 | 午夜精品一区二区三区在线视频 | 日韩高清无线码2023 | 日韩精品视频在线观看网址 | 一色av| 久久手机免费观看 | 91精品视频网站 | 在线观看一区视频 | 欧美日本三级 | 欧美激情综合五月色丁香 | 人人爽人人做 | 狠狠狠的干 | 亚洲国产大片 | 欧美一级特黄高清视频 | 久久99精品国产99久久6尤 | 99久久精品国产亚洲 | 粉嫩av一区二区三区免费 | 99久久精品国产系列 | 日韩视频免费观看高清完整版在线 | 日本三级久久 | 中文字幕亚洲情99在线 | 午夜精品一区二区三区在线 | 国产91在线免费视频 | 999久久久久久 | 丁香久久综合 | 欧美一区二区在线看 | 99久久精品国产一区二区三区 | 激情av资源网| 中文字幕刺激在线 | 91精品婷婷国产综合久久蝌蚪 | 超碰公开在线观看 | 欧美一区免费观看 | 久久经典国产 | 91福利试看 | 91精彩视频 | 日日爽夜夜爽 | 欧美激精品 | 欧美一级片在线观看视频 | 久久国产美女视频 | 18性欧美xxxⅹ性满足 | 一区二区三区高清在线观看 | 99精品国产一区二区三区麻豆 | 国产一区二区精品久久 | 久久精品小视频 | 国产视频一区二区在线播放 | 超级碰碰免费视频 | 国产黄a三级三级 | 四虎在线免费观看视频 | 国产精品一区二区三区在线免费观看 | 国产一级免费在线 | 日日操天天操狠狠操 | 日韩在线观看a | 欧美国产高清 | 久久手机免费观看 | 日本公妇在线观看高清 | 一区二区中文字幕在线 | 91香蕉视频在线 | av在线播放免费 | 一区二区三区在线观看免费视频 | 欧美激情综合色 | 国产探花视频在线播放 | 亚州精品天堂中文字幕 | 曰韩在线| 亚洲欧洲一区二区在线观看 | 欧美一区二区免费在线观看 | 精品国产免费久久 | 亚洲成人免费在线 | 亚洲精品视频在线观看免费视频 | 看av免费网站 | 欧美日韩后 | 久久亚洲精品电影 | 国产h在线观看 | 国产精品美女久久久久久久 | 国产精品短视频 | 精品少妇一区二区三区在线 | 国产日韩欧美在线免费观看 | 国产91小视频 | 曰本免费av | 国产高清免费av | 欧美激情精品久久 | 欧美日韩中文字幕综合视频 | 黄污网站在线观看 | 亚洲区精品视频 | 五月婷婷在线视频观看 | 永久免费精品视频 | 色综合久久久久综合体桃花网 | 免费av网址在线观看 | 国产一级a毛片视频爆浆 | 狠狠色狠狠色终合网 | 日韩激情小视频 | 国产激情电影综合在线看 | 麻豆av一区二区三区在线观看 | 国产正在播放 | 超级碰视频 | 日韩欧美在线中文字幕 | 国产亚洲欧美在线视频 | www.色婷婷.com | 国产在线观看地址 | 欧美性大胆 | 国产精品va在线播放 | 日韩精品免费 | 色com网| 精品在线观看一区二区三区 | 久久精品国产免费观看 | 男女男视频 | 亚洲成色777777在线观看影院 | 午夜影院一级片 | 啪啪小视频网站 | 不卡视频国产 | 亚洲国产一区二区精品专区 | 91欧美视频网站 | 黄色免费高清视频 | 久久超碰网 | 黄色av免费看 | 亚洲欧美日韩精品久久奇米一区 | 成人在线视频免费 | 色婷婷 亚洲 | 日本天天色 | 日日干av| 在线免费观看一区二区三区 | 国产精品12 | 国产成人精品一区二区三区网站观看 | 国产高清黄 | 国产精品99免视看9 国产精品毛片一区视频 | 久久久国产在线视频 | 久久精品视频国产 | 国产一级一片免费播放放a 一区二区三区国产欧美 | 久久高清 | 亚洲色图 校园春色 | 日日日日干 | 狠狠色丁香婷婷综合欧美 | 国产不卡精品 | 日韩在线视频网站 | 在线看片视频 | 精品国产一区二区三区av性色 | 黄色一级免费电影 | 国产高清在线a视频大全 | 国产麻豆精品久久一二三 | 97超碰在| 人人藻人人澡人人爽 | 四虎国产精品免费观看视频优播 | 久久久久久久久久电影 | 一级一级一片免费 | 久草在线观看视频免费 | 欧美综合在线视频 | 五月婷婷电影网 | 在线观看免费av网 | 又黄又爽的免费高潮视频 | 亚洲91精品在线观看 | 亚洲精品1区2区3区 超碰成人网 | 三级黄色免费 | 免费中文字幕视频 | av中文字幕网址 | 欧美激情精品 | 人人射人人插 | 久久免费视频2 | 久久亚洲综合国产精品99麻豆的功能介绍 | 国产精品视频永久免费播放 | 亚洲一区二区黄色 | 91网站免费观看 | 中文字幕永久 | 欧洲精品视频一区 | 黄av资源 | 成年人黄色大片在线 | 97精品国产97久久久久久粉红 | 探花视频免费观看 | 91视频麻豆 | 日韩精品免费一区二区在线观看 | 91亚洲网站| 中文字幕一区二区三区乱码不卡 | 91亚洲狠狠婷婷综合久久久 | av中文字幕在线播放 | 日韩天天综合 | 亚洲va韩国va欧美va精四季 | 免费福利小视频 | 亚洲国产欧美在线人成大黄瓜 | 色五婷婷 | 日韩免费观看一区二区 | 久久精品爱视频 | 99在线播放| 国产亚洲成人网 | 日韩欧美在线视频一区二区三区 | 天天干天天干天天色 | 久久精品国产精品亚洲精品 | 久久久免费观看完整版 | 久久精品99国产精品 | 久久99影院 | 亚洲成成品网站 | 欧美色噜噜噜 | 一区二区毛片 | 亚洲一区av | 在线播放av网址 | 免费看久久久 | 黄色com| 人人涩 | av三级在线免费观看 | 国产97免费 | 色婷婷在线观看视频 | 日韩电影中文字幕在线观看 | 国产黄色免费电影 | 99热官网| 精品99在线视频 | 黄色影院在线观看 | 色婷婷在线视频 | 国产日韩一区在线 | 日韩剧情 | 一区二区三区高清在线观看 | 欧美极品在线播放 | 中文字幕免费高清在线观看 | 18国产精品福利片久久婷 | 9在线观看免费高清完整版在线观看明 | 99操视频 | 久久极品| 国产精品永久在线 | 国产精品av久久久久久无 | 亚洲在线高清 | 婷婷五月色综合 | 亚洲精品乱码久久久久久蜜桃欧美 | 久久久激情网 | 久久国产精品久久久久 | 草在线 | 欧美日韩一区二区视频在线观看 | 国产做爰视频 | 精品一区二区6 | 日韩欧美国产免费播放 | 中文字幕在线看视频国产 | 久久综合九色九九 | 国产成在线观看免费视频 | 一级做a爱片性色毛片www | 天天操综合网站 | 天天综合精品 | 国内精品久久久久久久影视麻豆 | 一区二区中文字幕在线 | 免费看的黄色的网站 | 亚洲精品在线资源 | 亚洲精品videossex少妇 | 国产无限资源在线观看 | 日本久久成人中文字幕电影 | 成人毛片在线观看视频 | 天天曰夜夜爽 | 亚洲在线视频免费观看 | 在线观看视频你懂得 | 久久草草热国产精品直播 | 99热在线这里只有精品 | av久久久| 国产黄色高清 | 伊人国产在线观看 | 国产精品99久久久精品免费观看 | 亚洲三区在线 | 国产69精品久久app免费版 | 国产精品去看片 | 91日韩在线播放 | 精品久久一 | 在线观看视频国产一区 | 久草视频免费在线观看 | 成人在线免费看 | 激情欧美一区二区免费视频 | av高清一区二区三区 | 97超碰免费在线观看 | 久久综合一本 | 婷婷丁香在线观看 | 免费观看的黄色片 | 97色婷婷 | 婷婷久操 | 91香蕉视频在线 | 在线观看视频中文字幕 | 开心色激情网 | 操操操日日日干干干 | 久久私人影院 | 蜜臀久久99精品久久久无需会员 | av网站在线观看免费 | 99久久精品久久久久久动态片 | 日本不卡123| 国产人成在线观看 | 免费在线观看成人小视频 | 午夜视频在线瓜伦 | 亚洲精品88欧美一区二区 | 国产96在线 | av电影一区二区三区 | 国模精品一区二区三区 | 国产一级二级在线播放 | 人人插人人玩 | 九色视频网址 | 国产精品扒开做爽爽的视频 | 天堂av色婷婷一区二区三区 | 国产精品国产亚洲精品看不卡 | 福利区在线观看 | www.神马久久 | 欧美综合久久 | 少妇bbb搡bbbb搡bbbb | 在线影院av | 91| 黄色网址中文字幕 | 色 中文字幕 | 亚洲视频在线观看 | 久香蕉| 免费在线观看日韩欧美 | 中文字幕日韩一区二区三区不卡 | 日韩中文在线电影 | 99久久久成人国产精品 | 日本xxxx.com| 91日韩免费| 午夜精品久久久99热福利 | 91秒拍国产福利一区 | 狠狠躁18三区二区一区ai明星 | 午夜国产在线 | 欧美成人性战久久 | 亚洲日本黄色 | 最新久久免费视频 | 日韩免费电影一区二区 | 超碰在线人 | 九九综合九九 | 亚洲九九九在线观看 | 亚洲一级片免费观看 | 久久久精品小视频 | a视频在线| 天天色天天色 | 手机av在线免费观看 | 日韩免费一级a毛片在线播放一级 | 久久精品2| 欧美激情综合色综合啪啪五月 | 亚洲国产影院 | 日韩免费大片 | 久久艹人人 | 国产在线观看你懂的 | 手机av在线不卡 | 午夜视频播放 | 成人午夜黄色 | 久久av伊人| 日韩国产高清在线 | www.97视频| 青青色影院 | 香蕉在线视频播放网站 | 久久精品国产精品亚洲精品 | 久久久久久电影 | 激情婷婷综合网 | 日韩大片免费在线观看 | 一区在线观看 | 欧美成人黄 | 99精彩视频在线观看免费 | 久久亚洲在线 | 日韩.com| 中文字幕在线第一页 | 国产乱码精品一区二区三区介绍 | 亚洲精品国产精品国自产在线 | 日韩视频在线不卡 | 六月婷色 | 久久成人资源 | 1024在线看片 | 国产日产精品久久久久快鸭 | 久久免费视屏 | 久久国产精品免费一区二区三区 | 亚洲高清国产视频 | 天天操天天爱天天爽 | 中文字幕亚洲高清 | 国产成人精品久久久久 | 色婷婷狠狠操 | 婷婷久月 | 日韩黄色中文字幕 | 国产 一区二区三区 在线 | 日韩二区三区在线观看 | 超碰人人超 | 色综合亚洲精品激情狠狠 | 久久精品日产第一区二区三区乱码 | 日韩久久一区二区 | 日本成人免费在线观看 | 欧美整片sss | 国产在线精品一区二区三区 | 免费观看性生活大片3 | 久久精品国产一区二区三区 | 国产日产精品久久久久快鸭 | 婷婷日| 日韩二区三区 | 九草视频在线 | 2018好看的中文在线观看 | 国产精品久久久久久一二三四五 | 99热在线这里只有精品 | www.人人草 | 正在播放亚洲精品 | 丁香九月婷婷 | 91精品一区国产高清在线gif | 91超级碰碰 | 久久av一区二区三区亚洲 | 中文字幕中文字幕 | 久久国产三级 | 在线91视频 | 在线观看国产高清视频 | 国产日产欧美在线观看 | 天天爽天天做 | 成人在线免费观看视视频 | 狠狠躁18三区二区一区ai明星 | 日本在线中文 | 中文字幕一区二区三区四区久久 | 91喷水 | 99久久婷婷国产 | 黄色av免费在线 | 狠狠躁日日躁狂躁夜夜躁av | 免费在线h| 亚洲视频专区在线 | 四虎在线观看 | 日日日视频 | 欧美乱大交 | 夜夜看av| 久久久久久久久精 | 天天曰夜夜爽 | 国产一级a毛片视频爆浆 | 亚洲乱码国产乱码精品天美传媒 | 亚洲精品欧美成人 | 日韩中文字幕第一页 | 国产精品久久久久四虎 | 娇妻呻吟一区二区三区 | 色大片免费看 | 三级a毛片| 99久久精品午夜一区二区小说 | 国产视频精选在线 | 深爱激情综合网 | 欧美日韩网站 | 黄色成人在线 | 国产免费xvideos视频入口 | 在线免费黄 | 日韩av片无码一区二区不卡电影 | 久久99久久精品 | 99久久99久久 | 久久久久久久久亚洲精品 | av一级片在线观看 | 日韩在线观看电影 | 久草在线看片 | 欧美日韩三区二区 | 亚洲欧美日韩一二三区 | 又大又硬又黄又爽视频在线观看 | 免费一区在线 | 中文字幕4| 激情久久一区二区三区 | 97视频播放 | 久久国语露脸国产精品电影 | 久久久午夜影院 | 欧美一级免费黄色片 | 深爱五月激情五月 | 欧洲在线免费视频 | 丰满少妇对白在线偷拍 | 国产麻豆剧果冻传媒视频播放量 | 国产精品99久久99久久久二8 | 日韩在线观看小视频 | 黄视频色网站 | 激情av一区二区 | 国产一区二区三区视频在线 | 欧美一二三视频 | 激情五月av| 天天艹日日干 | 亚洲 综合 精品 | 久草在线手机视频 | 国精产品一二三线999 | 五月天激情视频 | 亚洲激情 在线 | 久久91久久久久麻豆精品 | 国产91勾搭技师精品 | 日韩高清观看 | 成人97人人超碰人人99 | 亚洲va天堂va欧美ⅴa在线 | 国产精品久久久久婷婷二区次 | 日日夜夜添 | 日日躁夜夜躁xxxxaaaa | 成人av在线看 | 97国产精品一区二区 | 国产呻吟在线 | 日韩欧美精品在线 | 五月婷婷综合色拍 | 97av在线视频| 免费看片成年人 | 日操操 | 国产黄网站在线观看 | 91精品资源| 欧美性久久久久久 | 成人av久久 | 婷婷中文字幕在线观看 | 国产一区二区影院 | 欧美精品在线免费 | 天天操天天干天天 | av三区在线| 久久人人精品 | 国产小视频免费在线观看 | 国产久草在线观看 | 中文字幕亚洲综合久久五月天色无吗'' | 成人在线观看你懂的 | 国产视频在线免费 | 国产精品成人一区二区三区吃奶 | av电影亚洲 | 欧美国产精品久久久久久免费 | www.av免费观看 | 高清视频一区 | 久久福利电影 | 天天操天天拍 | 日日操网 | 高清中文字幕 | 中文字幕免费国产精品 | av中文字幕av | 国内外成人免费在线视频 | 在线а√天堂中文官网 | 国产一区二区三区视频在线 | 日韩欧美在线一区二区 | 麻豆国产精品视频 | 日韩欧美在线高清 | 中文字幕高清视频 | 视频在线观看入口黄最新永久免费国产 | 亚洲精品资源在线 | 亚洲精品免费在线视频 | 97色在线视频 | 丁香花在线视频观看免费 | 免费一级片视频 | 五月天婷婷在线观看视频 | 99精品视频一区 | 九九在线视频 | 日韩综合一区二区 | 黄色网址在线播放 | 日韩动漫免费观看高清完整版在线观看 | 不卡的av电影在线观看 | 成人午夜在线观看 | 国产中文字幕第一页 | 婷婷在线免费视频 | 亚州精品天堂中文字幕 | 久久成人在线视频 | 婷婷六月中文字幕 | 色就色,综合激情 | 九九九热| 蜜桃视频日韩 | 欧美日韩视频观看 | www.夜夜骑.com | 国产精品免费久久久久久 | 久久久久久久久久国产精品 | 网站在线观看你们懂的 | 久久久久福利视频 | 欧美亚洲国产精品久久高清浪潮 | 99精品国产免费久久 | 天天天在线综合网 | 午夜成人影视 | 久久久久久综合 | 精品亚洲国产视频 | 亚洲免费精品一区二区 | av在线免费网 | 日本免费久久高清视频 | 日韩在观看线 | 国产福利在线免费 | 天天射天天色天天干 | 97在线观看视频国产 | 五月婷婷中文 | 亚洲一区二区黄色 | 欧美 日韩 国产 中文字幕 | 久久国产露脸精品国产 | 日韩免费观看一区二区 | 91最新在线 | 亚洲精品视 | 欧美日韩一区二区在线观看 | av电影免费看 | 美女一二三区 | 国产精品s色 | 色在线免费观看 | 国产一区二区免费看 | 国产91影院 | 国产精品视频 | 久久99国产精品自在自在app | 国产精品v欧美精品v日韩 | 欧美精品国产综合久久 | 激情av网址| www.com久久| 久久国产精品99久久久久久丝袜 | 精品国产一区二区在线 | 亚洲欧美激情插 | 三级av小说| 97香蕉视频 | 日韩黄色免费在线观看 | 久久a久久 | 国产在线自 | 亚洲精品久久久久999中文字幕 | 日本最大色倩网站www | 人人爽人人香蕉 | 麻豆一精品传二传媒短视频 | 国产剧情av在线播放 | 92精品国产成人观看免费 | 亚洲成人精品在线 | 97理论片| 91理论电影 | 蜜臀久久99精品久久久无需会员 | 国内精品久久久久影院优 | 丁香综合五月 | 一区二区三区四区在线免费观看 | 福利视频入口 | 久久a v电影 | 深夜免费小视频 | 久久不射电影院 | 亚洲高清精品在线 | 97电影网站| 天天爽天天射 | 天天爽夜夜爽精品视频婷婷 | 99精品久久99久久久久 | 免费麻豆 | www.香蕉视频在线观看 | 免费av黄色 | 亚洲色图色 | 久久综合影院 | 国产伦理一区二区 | 热久久免费国产视频 | 欧美日韩精品免费观看视频 | 国产综合精品一区二区三区 | 韩国精品一区二区三区六区色诱 | 精品国产精品久久 | 国产精品9999久久久久仙踪林 | 久草在线观看视频免费 | 在线播放国产精品 | 天天射天天射 | 日日爱网站| 国产精品精品国产色婷婷 | 日韩视频免费观看高清 | 国产黄在线看 | 日韩a级免费视频 | 国产污视频在线观看 | 亚洲天堂色婷婷 | 日韩精品久久一区二区三区 | 午夜精品视频一区 | 久久91久久久久麻豆精品 | 天天亚洲综合 | 久久999久久 | 激情欧美一区二区三区免费看 | 国产在线免费av | 国产精品 日韩 欧美 | 最近中文字幕免费大全 | 亚洲五月| 日韩黄色免费在线观看 | 日日摸日日 | 人人澡人人干 | 日韩一级电影在线 | 成人福利在线播放 | 亚洲最大激情中文字幕 | 国产性天天综合网 | 特级黄录像视频 | 高清av影院 | zzijzzij亚洲成熟少妇 | 日韩精品一区电影 | 特级免费毛片 | 日韩网页 | 精品国产免费一区二区三区五区 | 久久99网站 | 成人一区二区三区中文字幕 | 日本系列中文字幕 | 又黄又刺激又爽的视频 | 天天色天天搞 | 免费av观看网站 | 日本中文字幕在线免费观看 | 五月婷在线播放 | 国产亚洲精品精品精品 | 高清久久久久久 | 国产自偷自拍 | 在线亚洲观看 | 中文字幕传媒 | 国产精品自产拍在线观看蜜 | 久草视频免费看 | 黄色大片网 | 亚洲国产欧美在线看片xxoo | 亚洲 欧美变态 另类 综合 | 日韩网站在线播放 | 一区二区日韩av | 国产区精品在线 | 久久日本视频 | 国产aa精品 | 狠狠操操操 | 国内精品在线观看视频 | 色就是色综合 | 国产 精品 资源 | 久久精品网站免费观看 | 视频二区| av网站地址| 久久视频国产 | 天天色天天色 | 91精品久久久久久久91蜜桃 | 亚洲自拍自偷 | 精品国产自 | 丁五月婷婷 | 在线免费观看黄色小说 | 久久永久视频 | av一区在线播放 | 天天拍天天操 | 日韩高清一区 | 国产视频在线观看一区二区 | 国产精品中文字幕在线观看 | 五月天电影免费在线观看一区 | 久久精品视频网址 | 国产成人av电影在线观看 | 婷婷色狠狠 | 欧美日韩国产精品一区二区 | 精品久久久久久亚洲综合网站 | 久久99国产视频 | 国产小视频免费在线网址 | 欧美aaa大片| 午夜成人影视 | www.久久com | 欧洲激情综合 | 中文字幕视频三区 | 国产精品不卡视频 | 日本三级不卡视频 | 91在线观看高清 | 中文字幕av一区二区三区四区 | 激情丁香5月 | 中文字幕高清在线播放 | 99热精品国产一区二区在线观看 | 免费视频xnxx com | 国产日本在线观看 | 国产精品99页 | 毛片永久免费 | 日韩av中文在线 | 精品久久国产一区 | 久久久久久久久久久精 | 91女人18片女毛片60分钟 | 亚洲激情校园春色 | 五月婷婷在线观看视频 | 日韩av中文字幕在线 | 最新日韩视频 | 国产一区二区影院 | 黄a在线看 | 91视频91蝌蚪 | 99热在线观看免费 | 日韩成人免费在线观看 | 精品主播网红福利资源观看 | 久久夜av| 麻豆国产精品视频 | 超碰九九 | 亚洲国产欧洲综合997久久, | 免费看片日韩 | 激情五月婷婷综合 | 午夜视频在线观看一区 | 日韩在线中文字幕 | 免费h在线观看 | 91黄在线看 | 亚洲黄色免费电影 | 国产成人亚洲精品自产在线 | 国产亚洲精品久久久久5区 成人h电影在线观看 | 国产福利在线 | 精品视频在线免费 | 日韩在线高清免费视频 | 黄色91免费观看 | 久草在线视频网 | 激情久久久久 | 高清不卡毛片 | 在线观看免费福利 | 国产精品一区二区三区久久久 | www.黄色片网站 | 福利精品在线 | 日本三级人妇 | 99热最新地址 | 久久婷婷网 | 91中文字幕在线观看 | 中字幕视频在线永久在线观看免费 | 中文字幕免费 | 99久久www| 国产不卡av在线播放 | 成人免费网站在线观看 | 天天射,天天干 | 免费a视频在线观看 | 国产精品美女视频网站 | 操久久免费视频 | 久久国产欧美日韩 | 久久亚洲专区 | 永久免费精品视频网站 | 91免费的视频在线播放 | 五月天久久激情 | 草久在线| 国产美女精品视频免费观看 | 国产乱老熟视频网88av | 亚洲欧美视频在线播放 | 中文字幕精品三级久久久 | 国产自产在线视频 | 国产精品99久久免费黑人 | 五月天婷亚洲天综合网鲁鲁鲁 | 国产午夜麻豆影院在线观看 | 久久久精品国产免费观看同学 | 欧美在线一二区 | 麻豆视频91 | 天天干天天色2020 | 激情婷婷在线 | 91精品日韩 | 国产精品一区在线观看你懂的 | 日韩一二区在线观看 | 成人毛片在线观看视频 | 国产精品视频区 |