diff --git a/README.md b/README.md index b54f5c6..e4236fa 100644 --- a/README.md +++ b/README.md @@ -3,4 +3,5 @@ # 功能模块 [1.注册Mapper接口](https://github.com/FuriousPws002/mini-mybatis/wiki/1.%E6%B3%A8%E5%86%8CMapper%E6%8E%A5%E5%8F%A3 "Markdown")
[2.解析xml中静态sql的mapper](https://github.com/FuriousPws002/mini-mybatis/wiki/2.%E8%A7%A3%E6%9E%90xml%E4%B8%AD%E9%9D%99%E6%80%81sql%E7%9A%84mapper "Markdown")
-[3.执行静态sql](https://github.com/FuriousPws002/mini-mybatis/wiki/3.%E6%89%A7%E8%A1%8C%E9%9D%99%E6%80%81sql "Markdown")
\ No newline at end of file +[3.执行静态sql](https://github.com/FuriousPws002/mini-mybatis/wiki/3.%E6%89%A7%E8%A1%8C%E9%9D%99%E6%80%81sql "Markdown")
+[4.参数绑定](https://github.com/FuriousPws002/mini-mybatis/wiki/4.%E5%8F%82%E6%95%B0%E7%BB%91%E5%AE%9A "Markdown")
\ No newline at end of file diff --git a/src/main/java/org/apache/ibatis/annotations/Param.java b/src/main/java/org/apache/ibatis/annotations/Param.java new file mode 100644 index 0000000..6ffccd9 --- /dev/null +++ b/src/main/java/org/apache/ibatis/annotations/Param.java @@ -0,0 +1,18 @@ +package org.apache.ibatis.annotations; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author furious 2024/4/10 + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.PARAMETER) +public @interface Param { + + String value(); +} diff --git a/src/main/java/org/apache/ibatis/binding/MapperMethod.java b/src/main/java/org/apache/ibatis/binding/MapperMethod.java index 8bd3488..ffaf2d4 100644 --- a/src/main/java/org/apache/ibatis/binding/MapperMethod.java +++ b/src/main/java/org/apache/ibatis/binding/MapperMethod.java @@ -5,6 +5,7 @@ import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.mapping.SqlCommandType; +import org.apache.ibatis.reflection.ParamNameResolver; import org.apache.ibatis.session.Configuration; import org.apache.ibatis.session.SqlSession; @@ -14,16 +15,18 @@ public class MapperMethod { private final SqlCommand command; + private final MethodSignature method; public MapperMethod(Class mapperInterface, Method method, Configuration config) { this.command = new SqlCommand(config, mapperInterface, method); + this.method = new MethodSignature(config, mapperInterface, method); } public Object execute(SqlSession sqlSession, Object[] args) { Object result; switch (command.getType()) { case INSERT: { - result = sqlSession.insert(command.getName(), args); + result = sqlSession.insert(command.getName(), method.convertArgsToSqlCommandParam(args)); break; } default: @@ -57,4 +60,16 @@ public SqlCommandType getType() { } } + public static class MethodSignature { + + private final ParamNameResolver paramNameResolver; + + public MethodSignature(Configuration configuration, Class mapperInterface, Method method) { + this.paramNameResolver = new ParamNameResolver(method); + } + + public Object convertArgsToSqlCommandParam(Object[] args) { + return paramNameResolver.getNamedParams(args); + } + } } diff --git a/src/main/java/org/apache/ibatis/builder/SqlSourceBuilder.java b/src/main/java/org/apache/ibatis/builder/SqlSourceBuilder.java new file mode 100644 index 0000000..bbbe8ea --- /dev/null +++ b/src/main/java/org/apache/ibatis/builder/SqlSourceBuilder.java @@ -0,0 +1,49 @@ +package org.apache.ibatis.builder; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.ibatis.mapping.ParameterMapping; +import org.apache.ibatis.mapping.SqlSource; +import org.apache.ibatis.parsing.GenericTokenParser; +import org.apache.ibatis.parsing.TokenHandler; +import org.apache.ibatis.session.Configuration; + +/** + * @author furious 2024/4/10 + */ +public class SqlSourceBuilder extends BaseBuilder { + public SqlSourceBuilder(Configuration configuration) { + super(configuration); + } + + public SqlSource parse(String originalSql, Class parameterType) { + ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration); + GenericTokenParser parser = new GenericTokenParser("#{", "}", handler); + String sql = parser.parse(originalSql); + return new StaticSqlSource(sql, handler.getParameterMappings()); + } + + private static class ParameterMappingTokenHandler extends BaseBuilder implements TokenHandler { + + private List parameterMappings = new ArrayList<>(); + + public ParameterMappingTokenHandler(Configuration configuration) { + super(configuration); + } + + public List getParameterMappings() { + return parameterMappings; + } + + @Override + public String handleToken(String content) { + parameterMappings.add(buildParameterMapping(content)); + return "?"; + } + + private ParameterMapping buildParameterMapping(String content) { + return new ParameterMapping(configuration, content); + } + } +} \ No newline at end of file diff --git a/src/main/java/org/apache/ibatis/builder/StaticSqlSource.java b/src/main/java/org/apache/ibatis/builder/StaticSqlSource.java index dedbd87..8fca613 100644 --- a/src/main/java/org/apache/ibatis/builder/StaticSqlSource.java +++ b/src/main/java/org/apache/ibatis/builder/StaticSqlSource.java @@ -1,6 +1,9 @@ package org.apache.ibatis.builder; +import java.util.List; + import org.apache.ibatis.mapping.BoundSql; +import org.apache.ibatis.mapping.ParameterMapping; import org.apache.ibatis.mapping.SqlSource; /** @@ -9,13 +12,19 @@ public class StaticSqlSource implements SqlSource { private final String sql; + private final List parameterMappings; public StaticSqlSource(String sql) { + this(sql, null); + } + + public StaticSqlSource(String sql, List parameterMappings) { this.sql = sql; + this.parameterMappings = parameterMappings; } @Override public BoundSql getBoundSql(Object parameterObject) { - return new BoundSql(this.sql); + return new BoundSql(sql, parameterMappings, parameterObject); } } diff --git a/src/main/java/org/apache/ibatis/builder/xml/XMLStatementBuilder.java b/src/main/java/org/apache/ibatis/builder/xml/XMLStatementBuilder.java index a0b2fe0..d8d0ba5 100644 --- a/src/main/java/org/apache/ibatis/builder/xml/XMLStatementBuilder.java +++ b/src/main/java/org/apache/ibatis/builder/xml/XMLStatementBuilder.java @@ -35,7 +35,7 @@ public void parseStatementNode() { String id = context.getAttribute("id"); String nodeName = context.getName(); SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH)); - SqlSource sqlSource = new StaticSqlSource(context.getBody()); + SqlSource sqlSource = configuration.getLanguageDriver().createSqlSource(configuration, context, null); builderAssistant.addMappedStatement(id, sqlCommandType, sqlSource); } } diff --git a/src/main/java/org/apache/ibatis/executor/SimpleExecutor.java b/src/main/java/org/apache/ibatis/executor/SimpleExecutor.java index 0af4dbf..46af77b 100644 --- a/src/main/java/org/apache/ibatis/executor/SimpleExecutor.java +++ b/src/main/java/org/apache/ibatis/executor/SimpleExecutor.java @@ -41,6 +41,7 @@ private Statement prepareStatement(StatementHandler handler) throws SQLException Connection connection = configuration.getDataSource().getConnection(); connection.setAutoCommit(true); stmt = handler.prepare(connection, null); + handler.parameterize(stmt); return stmt; } diff --git a/src/main/java/org/apache/ibatis/executor/parameter/ParameterHandler.java b/src/main/java/org/apache/ibatis/executor/parameter/ParameterHandler.java new file mode 100644 index 0000000..94ba67b --- /dev/null +++ b/src/main/java/org/apache/ibatis/executor/parameter/ParameterHandler.java @@ -0,0 +1,14 @@ +package org.apache.ibatis.executor.parameter; + +import java.sql.PreparedStatement; +import java.sql.SQLException; + +/** + * @author furious 2024/4/10 + */ +public interface ParameterHandler { + + Object getParameterObject(); + + void setParameters(PreparedStatement ps) throws SQLException; +} diff --git a/src/main/java/org/apache/ibatis/executor/statement/PrepareStatementHandler.java b/src/main/java/org/apache/ibatis/executor/statement/PrepareStatementHandler.java index e00c449..e9be854 100644 --- a/src/main/java/org/apache/ibatis/executor/statement/PrepareStatementHandler.java +++ b/src/main/java/org/apache/ibatis/executor/statement/PrepareStatementHandler.java @@ -7,6 +7,7 @@ import java.util.Objects; import org.apache.ibatis.executor.Executor; +import org.apache.ibatis.executor.parameter.ParameterHandler; import org.apache.ibatis.mapping.BoundSql; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.session.Configuration; @@ -20,6 +21,7 @@ public class PrepareStatementHandler implements StatementHandler { protected final Executor executor; protected final MappedStatement mappedStatement; protected BoundSql boundSql; + protected final ParameterHandler parameterHandler; public PrepareStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) { this.configuration = mappedStatement.getConfiguration(); @@ -29,6 +31,7 @@ public PrepareStatementHandler(Executor executor, MappedStatement mappedStatemen boundSql = mappedStatement.getBoundSql(parameterObject); } this.boundSql = boundSql; + this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql); } @Override @@ -42,4 +45,9 @@ public int update(Statement statement) throws SQLException { ps.execute(); return ps.getUpdateCount(); } + + @Override + public void parameterize(Statement statement) throws SQLException { + parameterHandler.setParameters((PreparedStatement) statement); + } } diff --git a/src/main/java/org/apache/ibatis/executor/statement/StatementHandler.java b/src/main/java/org/apache/ibatis/executor/statement/StatementHandler.java index ac02e3d..7395ac5 100644 --- a/src/main/java/org/apache/ibatis/executor/statement/StatementHandler.java +++ b/src/main/java/org/apache/ibatis/executor/statement/StatementHandler.java @@ -12,4 +12,6 @@ public interface StatementHandler { Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException; int update(Statement statement) throws SQLException; + + void parameterize(Statement statement) throws SQLException; } diff --git a/src/main/java/org/apache/ibatis/mapping/BoundSql.java b/src/main/java/org/apache/ibatis/mapping/BoundSql.java index b9a55a7..83ac02c 100644 --- a/src/main/java/org/apache/ibatis/mapping/BoundSql.java +++ b/src/main/java/org/apache/ibatis/mapping/BoundSql.java @@ -1,14 +1,32 @@ package org.apache.ibatis.mapping; +import java.util.List; + /** * @author furious 2024/4/7 */ public class BoundSql { private final String sql; + private final List parameterMappings; + private final Object parameterObject; public BoundSql(String sql) { + this(sql, null, null); + } + + public BoundSql(String sql, List parameterMappings, Object parameterObject) { this.sql = sql; + this.parameterMappings = parameterMappings; + this.parameterObject = parameterObject; + } + + public List getParameterMappings() { + return parameterMappings; + } + + public Object getParameterObject() { + return parameterObject; } public String getSql() { diff --git a/src/main/java/org/apache/ibatis/mapping/MappedStatement.java b/src/main/java/org/apache/ibatis/mapping/MappedStatement.java index d1bb08b..8f1d452 100644 --- a/src/main/java/org/apache/ibatis/mapping/MappedStatement.java +++ b/src/main/java/org/apache/ibatis/mapping/MappedStatement.java @@ -27,7 +27,7 @@ public Configuration getConfiguration() { public BoundSql getBoundSql(Object parameterObject) { BoundSql boundSql = sqlSource.getBoundSql(parameterObject); - return new BoundSql(boundSql.getSql()); + return new BoundSql(boundSql.getSql(),boundSql.getParameterMappings(),parameterObject); } public static class Builder { diff --git a/src/main/java/org/apache/ibatis/mapping/ParameterMapping.java b/src/main/java/org/apache/ibatis/mapping/ParameterMapping.java new file mode 100644 index 0000000..cc5f519 --- /dev/null +++ b/src/main/java/org/apache/ibatis/mapping/ParameterMapping.java @@ -0,0 +1,56 @@ +package org.apache.ibatis.mapping; + +import org.apache.ibatis.session.Configuration; +import org.apache.ibatis.type.TypeHandler; + +/** + * @author furious 2024/4/10 + */ +public class ParameterMapping { + + private Configuration configuration; + private String property; + private Class javaType = Object.class; + private TypeHandler typeHandler; + + public ParameterMapping() { + } + + public ParameterMapping(Configuration configuration, String property) { + this.configuration = configuration; + this.property = property; + this.typeHandler = configuration.getTypeHandlerRegistry().getUnknownTypeHandler(); + } + + public Configuration getConfiguration() { + return configuration; + } + + public void setConfiguration(Configuration configuration) { + this.configuration = configuration; + } + + public String getProperty() { + return property; + } + + public void setProperty(String property) { + this.property = property; + } + + public Class getJavaType() { + return javaType; + } + + public void setJavaType(Class javaType) { + this.javaType = javaType; + } + + public TypeHandler getTypeHandler() { + return typeHandler; + } + + public void setTypeHandler(TypeHandler typeHandler) { + this.typeHandler = typeHandler; + } +} diff --git a/src/main/java/org/apache/ibatis/parsing/GenericTokenParser.java b/src/main/java/org/apache/ibatis/parsing/GenericTokenParser.java new file mode 100644 index 0000000..b0abbf0 --- /dev/null +++ b/src/main/java/org/apache/ibatis/parsing/GenericTokenParser.java @@ -0,0 +1,31 @@ +package org.apache.ibatis.parsing; + +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.StringUtils; + +/** + * @author furious 2024/4/10 + */ +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; + } + + public String parse(String text) { + String[] tokens = StringUtils.substringsBetween(text, openToken, closeToken); + if (ArrayUtils.isEmpty(tokens)) { + return text; + } + for (String token : tokens) { + text = text.replace(openToken + token + closeToken, handler.handleToken(token)); + } + return text; + } +} diff --git a/src/main/java/org/apache/ibatis/parsing/TokenHandler.java b/src/main/java/org/apache/ibatis/parsing/TokenHandler.java new file mode 100644 index 0000000..f1cf802 --- /dev/null +++ b/src/main/java/org/apache/ibatis/parsing/TokenHandler.java @@ -0,0 +1,9 @@ +package org.apache.ibatis.parsing; + +/** + * @author furious 2024/4/10 + */ +public interface TokenHandler { + + String handleToken(String content); +} diff --git a/src/main/java/org/apache/ibatis/parsing/XNode.java b/src/main/java/org/apache/ibatis/parsing/XNode.java index fdafaaa..e664858 100644 --- a/src/main/java/org/apache/ibatis/parsing/XNode.java +++ b/src/main/java/org/apache/ibatis/parsing/XNode.java @@ -30,6 +30,10 @@ public XNode(XPathParser xpathParser, Node node) { this.body = parseBody(node); } + public XNode newXNode(Node node) { + return new XNode(xpathParser, node); + } + public T getAttribute(String name) { return (T) attributes.getProperty(name); } @@ -46,6 +50,10 @@ public String getName() { return name; } + public Node getNode() { + return node; + } + private Properties parseAttributes(Node n) { Properties attributes = new Properties(); NamedNodeMap attributeNodes = n.getAttributes(); diff --git a/src/main/java/org/apache/ibatis/reflection/ParamNameResolver.java b/src/main/java/org/apache/ibatis/reflection/ParamNameResolver.java new file mode 100644 index 0000000..d60e2a2 --- /dev/null +++ b/src/main/java/org/apache/ibatis/reflection/ParamNameResolver.java @@ -0,0 +1,63 @@ +package org.apache.ibatis.reflection; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.SortedMap; +import java.util.TreeMap; + +import org.apache.ibatis.annotations.Param; + +/** + * @author furious 2024/4/10 + */ +public class ParamNameResolver { + + private boolean hasParamAnnotation; + + /** + * map中key为参数序号,value为参数名称 + * method(@Param("M") int a, @Param("N") int b) -> {{0, "M"}, {1, "N"}} + */ + private final SortedMap names; + + public ParamNameResolver(Method method) { + final Annotation[][] paramAnnotations = method.getParameterAnnotations(); + final SortedMap map = new TreeMap<>(); + int paramCount = paramAnnotations.length; + for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) { + String name = null; + for (Annotation annotation : paramAnnotations[paramIndex]) { + if (annotation instanceof Param) { + name = ((Param) annotation).value(); + hasParamAnnotation = true; + break; + } + } + if (Objects.isNull(name)) { + name = method.getParameters()[paramIndex].getName(); + } + map.put(paramIndex, name); + } + names = Collections.unmodifiableSortedMap(map); + } + + public Object getNamedParams(Object[] args) { + final int paramCount = names.size(); + if (Objects.isNull(args) || paramCount == 0) { + return null; + } else if (!hasParamAnnotation && paramCount == 1) { + //没有Param注解,同时只有一个参数,直接返回这个参数值 + return args[0]; + } else { + final Map param = new HashMap<>(); + for (Map.Entry entry : names.entrySet()) { + param.put(entry.getValue(), args[entry.getKey()]); + } + return param; + } + } +} diff --git a/src/main/java/org/apache/ibatis/scripting/LanguageDriver.java b/src/main/java/org/apache/ibatis/scripting/LanguageDriver.java new file mode 100644 index 0000000..3f1d21a --- /dev/null +++ b/src/main/java/org/apache/ibatis/scripting/LanguageDriver.java @@ -0,0 +1,18 @@ +package org.apache.ibatis.scripting; + +import org.apache.ibatis.executor.parameter.ParameterHandler; +import org.apache.ibatis.mapping.BoundSql; +import org.apache.ibatis.mapping.MappedStatement; +import org.apache.ibatis.mapping.SqlSource; +import org.apache.ibatis.parsing.XNode; +import org.apache.ibatis.session.Configuration; + +/** + * @author furious 2024/4/10 + */ +public interface LanguageDriver { + + ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql); + + SqlSource createSqlSource(Configuration configuration, XNode script, Class parameterType); +} diff --git a/src/main/java/org/apache/ibatis/scripting/defaults/DefaultParameterHandler.java b/src/main/java/org/apache/ibatis/scripting/defaults/DefaultParameterHandler.java new file mode 100644 index 0000000..952dade --- /dev/null +++ b/src/main/java/org/apache/ibatis/scripting/defaults/DefaultParameterHandler.java @@ -0,0 +1,68 @@ +package org.apache.ibatis.scripting.defaults; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import org.apache.commons.lang3.reflect.FieldUtils; +import org.apache.ibatis.executor.parameter.ParameterHandler; +import org.apache.ibatis.mapping.BoundSql; +import org.apache.ibatis.mapping.MappedStatement; +import org.apache.ibatis.mapping.ParameterMapping; +import org.apache.ibatis.session.Configuration; +import org.apache.ibatis.type.TypeException; + +/** + * @author furious 2024/4/10 + */ +public class DefaultParameterHandler implements ParameterHandler { + + private final MappedStatement mappedStatement; + private final Object parameterObject; + private final BoundSql boundSql; + private final Configuration configuration; + + public DefaultParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) { + this.mappedStatement = mappedStatement; + this.configuration = mappedStatement.getConfiguration(); + this.parameterObject = parameterObject; + this.boundSql = boundSql; + } + + @Override + public Object getParameterObject() { + return parameterObject; + } + + @Override + @SuppressWarnings({"unchecked", "rawtypes"}) + public void setParameters(PreparedStatement ps) throws SQLException { + List parameterMappings = boundSql.getParameterMappings(); + if (Objects.isNull(parameterMappings)) { + return; + } + for (int i = 0; i < parameterMappings.size(); i++) { + ParameterMapping parameterMapping = parameterMappings.get(i); + Object value; + String propertyName = parameterMapping.getProperty(); + if (Objects.isNull(parameterObject)) { + value = null; + } else if (configuration.getTypeHandlerRegistry().hasTypeHandler(parameterObject.getClass())) { + value = parameterObject; + } else if (parameterObject instanceof Map) { + //parameterObject为map,org.apache.ibatis.reflection.ParamNameResolver.getNamedParams + value = ((Map) parameterObject).get(propertyName); + } else { + //pojo对象,通过反射获取对象值 + try { + value = FieldUtils.readField(parameterObject, propertyName, true); + } catch (Exception e) { + throw new TypeException(e); + } + } + parameterMapping.getTypeHandler().setParameter(ps, i + 1, value); + } + } +} diff --git a/src/main/java/org/apache/ibatis/scripting/defaults/RawSqlSource.java b/src/main/java/org/apache/ibatis/scripting/defaults/RawSqlSource.java new file mode 100644 index 0000000..63bf0b4 --- /dev/null +++ b/src/main/java/org/apache/ibatis/scripting/defaults/RawSqlSource.java @@ -0,0 +1,37 @@ +package org.apache.ibatis.scripting.defaults; + +import org.apache.ibatis.builder.SqlSourceBuilder; +import org.apache.ibatis.mapping.BoundSql; +import org.apache.ibatis.mapping.SqlSource; +import org.apache.ibatis.scripting.xmltags.DynamicContext; +import org.apache.ibatis.scripting.xmltags.SqlNode; +import org.apache.ibatis.session.Configuration; + +/** + * @author furious 2024/4/10 + */ +public class RawSqlSource implements SqlSource { + + private final SqlSource sqlSource; + + public RawSqlSource(Configuration configuration, SqlNode rootSqlNode, Class parameterType) { + this(configuration, getSql(rootSqlNode), parameterType); + } + + public RawSqlSource(Configuration configuration, String sql, Class parameterType) { + SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration); + Class clazz = parameterType == null ? Object.class : parameterType; + sqlSource = sqlSourceParser.parse(sql, clazz); + } + + private static String getSql(SqlNode rootSqlNode) { + DynamicContext context = new DynamicContext(); + rootSqlNode.apply(context); + return context.getSql(); + } + + @Override + public BoundSql getBoundSql(Object parameterObject) { + return sqlSource.getBoundSql(parameterObject); + } +} diff --git a/src/main/java/org/apache/ibatis/scripting/xmltags/DynamicContext.java b/src/main/java/org/apache/ibatis/scripting/xmltags/DynamicContext.java new file mode 100644 index 0000000..15822b8 --- /dev/null +++ b/src/main/java/org/apache/ibatis/scripting/xmltags/DynamicContext.java @@ -0,0 +1,22 @@ +package org.apache.ibatis.scripting.xmltags; + +import java.util.StringJoiner; + +/** + * @author furious 2024/4/10 + */ +public class DynamicContext { + + private final StringJoiner sqlBuilder = new StringJoiner(" "); + + public DynamicContext() { + } + + public void appendSql(String sql) { + sqlBuilder.add(sql); + } + + public String getSql() { + return sqlBuilder.toString().trim(); + } +} diff --git a/src/main/java/org/apache/ibatis/scripting/xmltags/MixedSqlNode.java b/src/main/java/org/apache/ibatis/scripting/xmltags/MixedSqlNode.java new file mode 100644 index 0000000..ac9189a --- /dev/null +++ b/src/main/java/org/apache/ibatis/scripting/xmltags/MixedSqlNode.java @@ -0,0 +1,21 @@ +package org.apache.ibatis.scripting.xmltags; + +import java.util.List; + +/** + * @author furious 2024/4/10 + */ +public class MixedSqlNode implements SqlNode { + + private final List contents; + + public MixedSqlNode(List contents) { + this.contents = contents; + } + + @Override + public boolean apply(DynamicContext context) { + contents.forEach(node -> node.apply(context)); + return true; + } +} diff --git a/src/main/java/org/apache/ibatis/scripting/xmltags/SqlNode.java b/src/main/java/org/apache/ibatis/scripting/xmltags/SqlNode.java new file mode 100644 index 0000000..de0bdfe --- /dev/null +++ b/src/main/java/org/apache/ibatis/scripting/xmltags/SqlNode.java @@ -0,0 +1,9 @@ +package org.apache.ibatis.scripting.xmltags; + +/** + * @author furious 2024/4/10 + */ +public interface SqlNode { + + boolean apply(DynamicContext context); +} diff --git a/src/main/java/org/apache/ibatis/scripting/xmltags/StaticTextSqlNode.java b/src/main/java/org/apache/ibatis/scripting/xmltags/StaticTextSqlNode.java new file mode 100644 index 0000000..5d95d09 --- /dev/null +++ b/src/main/java/org/apache/ibatis/scripting/xmltags/StaticTextSqlNode.java @@ -0,0 +1,19 @@ +package org.apache.ibatis.scripting.xmltags; + +/** + * @author furious 2024/4/10 + */ +public class StaticTextSqlNode implements SqlNode { + + private final String text; + + public StaticTextSqlNode(String text) { + this.text = text; + } + + @Override + public boolean apply(DynamicContext context) { + context.appendSql(text); + return true; + } +} \ No newline at end of file diff --git a/src/main/java/org/apache/ibatis/scripting/xmltags/XMLLanguageDriver.java b/src/main/java/org/apache/ibatis/scripting/xmltags/XMLLanguageDriver.java new file mode 100644 index 0000000..4c12d8e --- /dev/null +++ b/src/main/java/org/apache/ibatis/scripting/xmltags/XMLLanguageDriver.java @@ -0,0 +1,26 @@ +package org.apache.ibatis.scripting.xmltags; + +import org.apache.ibatis.executor.parameter.ParameterHandler; +import org.apache.ibatis.mapping.BoundSql; +import org.apache.ibatis.mapping.MappedStatement; +import org.apache.ibatis.mapping.SqlSource; +import org.apache.ibatis.parsing.XNode; +import org.apache.ibatis.scripting.LanguageDriver; +import org.apache.ibatis.scripting.defaults.DefaultParameterHandler; +import org.apache.ibatis.session.Configuration; + +/** + * @author furious 2024/4/10 + */ +public class XMLLanguageDriver implements LanguageDriver { + + @Override + public ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) { + return new DefaultParameterHandler(mappedStatement, parameterObject, boundSql); + } + + @Override + public SqlSource createSqlSource(Configuration configuration, XNode script, Class parameterType) { + return new XMLScriptBuilder(configuration, script, parameterType).parseScriptNode(); + } +} diff --git a/src/main/java/org/apache/ibatis/scripting/xmltags/XMLScriptBuilder.java b/src/main/java/org/apache/ibatis/scripting/xmltags/XMLScriptBuilder.java new file mode 100644 index 0000000..b0a82c5 --- /dev/null +++ b/src/main/java/org/apache/ibatis/scripting/xmltags/XMLScriptBuilder.java @@ -0,0 +1,48 @@ +package org.apache.ibatis.scripting.xmltags; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.ibatis.builder.BaseBuilder; +import org.apache.ibatis.mapping.SqlSource; +import org.apache.ibatis.parsing.XNode; +import org.apache.ibatis.scripting.defaults.RawSqlSource; +import org.apache.ibatis.session.Configuration; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +/** + * @author furious 2024/4/10 + */ +public class XMLScriptBuilder extends BaseBuilder { + + private final XNode context; + private final Class parameterType; + + public XMLScriptBuilder(Configuration configuration, XNode context) { + this(configuration, context, null); + } + + public XMLScriptBuilder(Configuration configuration, XNode context, Class parameterType) { + super(configuration); + this.context = context; + this.parameterType = parameterType; + } + + public SqlSource parseScriptNode() { + MixedSqlNode rootSqlNode = parseDynamicTags(context); + return new RawSqlSource(configuration, rootSqlNode, parameterType); + } + + protected MixedSqlNode parseDynamicTags(XNode node) { + List contents = new ArrayList<>(); + 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.TEXT_NODE) { + contents.add(new StaticTextSqlNode(child.getBody())); + } + } + return new MixedSqlNode(contents); + } +} diff --git a/src/main/java/org/apache/ibatis/session/Configuration.java b/src/main/java/org/apache/ibatis/session/Configuration.java index 7417149..a75f281 100644 --- a/src/main/java/org/apache/ibatis/session/Configuration.java +++ b/src/main/java/org/apache/ibatis/session/Configuration.java @@ -10,10 +10,13 @@ import org.apache.ibatis.binding.MapperRegistry; import org.apache.ibatis.executor.Executor; +import org.apache.ibatis.executor.parameter.ParameterHandler; import org.apache.ibatis.executor.statement.PrepareStatementHandler; import org.apache.ibatis.executor.statement.StatementHandler; import org.apache.ibatis.mapping.BoundSql; import org.apache.ibatis.mapping.MappedStatement; +import org.apache.ibatis.scripting.LanguageDriver; +import org.apache.ibatis.scripting.xmltags.XMLLanguageDriver; import org.apache.ibatis.type.TypeHandlerRegistry; /** @@ -29,6 +32,7 @@ public class Configuration { private final Set loadedResources = new HashSet<>(); private final Map mappedStatements = new HashMap<>(); private final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry(); + private final LanguageDriver languageDriver = new XMLLanguageDriver(); public Configuration() { } @@ -77,7 +81,15 @@ public StatementHandler newStatementHandler(Executor executor, MappedStatement m return new PrepareStatementHandler(executor, mappedStatement, parameterObject, boundSql); } + public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) { + return languageDriver.createParameterHandler(mappedStatement, parameterObject, boundSql); + } + public TypeHandlerRegistry getTypeHandlerRegistry() { return typeHandlerRegistry; } + + public LanguageDriver getLanguageDriver() { + return languageDriver; + } } diff --git a/src/main/java/org/apache/ibatis/session/defaults/DefaultSqlSession.java b/src/main/java/org/apache/ibatis/session/defaults/DefaultSqlSession.java index 000e5ad..0fb003d 100644 --- a/src/main/java/org/apache/ibatis/session/defaults/DefaultSqlSession.java +++ b/src/main/java/org/apache/ibatis/session/defaults/DefaultSqlSession.java @@ -48,7 +48,7 @@ public int insert(String statement, Object parameter) { public int update(String statement, Object parameter) { try { MappedStatement ms = configuration.getMappedStatement(statement); - return executor.update(ms, null); + return executor.update(ms, parameter); } catch (Exception e) { throw new RuntimeException(e); } diff --git a/src/main/java/org/apache/ibatis/type/TypeException.java b/src/main/java/org/apache/ibatis/type/TypeException.java new file mode 100644 index 0000000..c8e3ee6 --- /dev/null +++ b/src/main/java/org/apache/ibatis/type/TypeException.java @@ -0,0 +1,27 @@ +package org.apache.ibatis.type; + +/** + * @author furious 2024/4/10 + */ +public class TypeException extends RuntimeException{ + + public TypeException() { + super(); + } + + public TypeException(String message) { + super(message); + } + + public TypeException(String message, Throwable cause) { + super(message, cause); + } + + public TypeException(Throwable cause) { + super(cause); + } + + protected TypeException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/src/main/java/org/apache/ibatis/type/TypeHandlerRegistry.java b/src/main/java/org/apache/ibatis/type/TypeHandlerRegistry.java index 8f640fe..0fd398d 100644 --- a/src/main/java/org/apache/ibatis/type/TypeHandlerRegistry.java +++ b/src/main/java/org/apache/ibatis/type/TypeHandlerRegistry.java @@ -2,6 +2,7 @@ import java.util.HashMap; import java.util.Map; +import java.util.Objects; /** * @author furious 2024/4/9 @@ -32,4 +33,8 @@ public TypeHandler getTypeHandler(Class type) { public TypeHandler getUnknownTypeHandler() { return unknownTypeHandler; } + + public boolean hasTypeHandler(Class type) { + return Objects.nonNull(getTypeHandler(type)); + } } diff --git a/src/test/java/org/apache/ibatis/dao/UserMapper.java b/src/test/java/org/apache/ibatis/dao/UserMapper.java index 35a65a5..5ab66ef 100644 --- a/src/test/java/org/apache/ibatis/dao/UserMapper.java +++ b/src/test/java/org/apache/ibatis/dao/UserMapper.java @@ -1,9 +1,29 @@ package org.apache.ibatis.dao; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.entity.UserDO; +import org.apache.ibatis.reflection.ParamNameResolver; +import org.apache.ibatis.scripting.defaults.DefaultParameterHandler; + /** * @author furious 2024/4/3 */ public interface UserMapper { void insert(); + + void insertWithParam(@Param("name") String name, @Param("age") Integer age); + + /** + * 单个参数没有设置Param注解时,无需参数名称和xml中变量名相同 + * 见ParamNameResolver中getNamedParams方法,无Param注解,只有一个参数时直接返回值 + * 见DefaultParameterHandler中set方法,String类型已注册Handler,直接返回值 + * + * @param namex 参数名称 + * @see ParamNameResolver#getNamedParams(java.lang.Object[]) + * @see DefaultParameterHandler#setParameters(java.sql.PreparedStatement) + */ + void insertWithoutParam(String namex); + + void insertWithPOJO(UserDO user); } diff --git a/src/test/java/org/apache/ibatis/entity/UserDO.java b/src/test/java/org/apache/ibatis/entity/UserDO.java new file mode 100644 index 0000000..1b1667f --- /dev/null +++ b/src/test/java/org/apache/ibatis/entity/UserDO.java @@ -0,0 +1,26 @@ +package org.apache.ibatis.entity; + +/** + * @author furious 2024/4/10 + */ +public class UserDO { + + private String name; + private Integer age; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Integer getAge() { + return age; + } + + public void setAge(Integer age) { + this.age = age; + } +} diff --git a/src/test/java/org/apache/ibatis/session/SqlSessionTest.java b/src/test/java/org/apache/ibatis/session/SqlSessionTest.java index 8545522..ca08435 100644 --- a/src/test/java/org/apache/ibatis/session/SqlSessionTest.java +++ b/src/test/java/org/apache/ibatis/session/SqlSessionTest.java @@ -2,6 +2,7 @@ import org.apache.ibatis.DataSourceBuilderTest; import org.apache.ibatis.dao.UserMapper; +import org.apache.ibatis.entity.UserDO; import org.apache.ibatis.session.defaults.DefaultSqlSession; import org.junit.Test; @@ -19,4 +20,60 @@ public void staticSqlExecution() { UserMapper userMapper = sqlSession.getMapper(UserMapper.class); userMapper.insert(); } + + /** + * Param注解绑定参数 + */ + @Test + public void insertWithParam() { + Configuration configuration = new Configuration(); + configuration.setDataSource(DataSourceBuilderTest.build()); + configuration.addMapper(UserMapper.class); + SqlSession sqlSession = new DefaultSqlSession(configuration); + UserMapper userMapper = sqlSession.getMapper(UserMapper.class); + userMapper.insertWithParam("Alice", 23); + } + + /** + * 支持传null + */ + @Test + public void insertWithParamNullable() { + Configuration configuration = new Configuration(); + configuration.setDataSource(DataSourceBuilderTest.build()); + configuration.addMapper(UserMapper.class); + SqlSession sqlSession = new DefaultSqlSession(configuration); + UserMapper userMapper = sqlSession.getMapper(UserMapper.class); + userMapper.insertWithParam("Jack", null); + } + + /** + * 无Param注解的单个参数 + * 单个参数没有设置Param注解时,无需参数名称和xml中变量名相同 + */ + @Test + public void insertWithoutParam() { + Configuration configuration = new Configuration(); + configuration.setDataSource(DataSourceBuilderTest.build()); + configuration.addMapper(UserMapper.class); + SqlSession sqlSession = new DefaultSqlSession(configuration); + UserMapper userMapper = sqlSession.getMapper(UserMapper.class); + userMapper.insertWithoutParam("Tom"); + } + + /** + * 简单对象 + */ + @Test + public void insertWithPOJO() { + Configuration configuration = new Configuration(); + configuration.setDataSource(DataSourceBuilderTest.build()); + configuration.addMapper(UserMapper.class); + SqlSession sqlSession = new DefaultSqlSession(configuration); + UserMapper userMapper = sqlSession.getMapper(UserMapper.class); + UserDO user = new UserDO(); + user.setName("Sam"); + user.setAge(20); + userMapper.insertWithPOJO(user); + } } diff --git a/src/test/resources/mapper/UserMapper.xml b/src/test/resources/mapper/UserMapper.xml index fbaa4a3..1e04d87 100644 --- a/src/test/resources/mapper/UserMapper.xml +++ b/src/test/resources/mapper/UserMapper.xml @@ -5,4 +5,15 @@ INSERT INTO user (name,age,birth) VALUES ('Bob',25,'2024-03-29 22:09:07') + + INSERT INTO user (name,age,birth) VALUES (#{name},#{age},'2024-03-29 22:09:07') + + + + INSERT INTO user (name,age,birth) VALUES (#{name},18,'2024-03-29 22:09:07') + + + + INSERT INTO user (name,age,birth) VALUES (#{name},#{age},'2024-03-29 22:09:07') + \ No newline at end of file