The original link
JDBC Problem Analysis
Existing problems
- The database configuration information is hard coded.
- Frequently create release database connections.
- SQL statement, set parameters, obtain results parameters are hard coding problems.
- Manual encapsulation of the returned result set is cumbersome.
The solution
-
Through the configuration file
- The connection pool
- SQL configuration file
- Reflection, introspection
Analysis of the framework of custom persistence layer
Essentially, it encapsulates JDBC code
1. The train of thought
Client (User)
-
Introduces jar packages for custom persistence frameworks
-
The client provides two parts of configuration information:
- Database configuration information: driver classes, database connections, accounts, and passwords
- SQL configuration information: SQL statement, parameter type, return value type
Use the configuration file to provide these two pieces of information:
Sqlmapconfiguration. XML: stores database configuration information. Full path to mapper.xml (only loaded once for later loading of configuration files)
(2) mapper. XML: SQL configuration information exists
- The client gets the configuration file input stream (Resources) and uses SqlSessionFactoryBuilder to get the SqlSession to execute the query
Customize the persistence layer framework itself
-
Loading a configuration file: Loads the configuration file as a byte input stream and stores it in memory
- Resources class: InputSteam getResourceAsSteam(String path)
-
Create two Java classes (container objects) that hold the content parsed from the configuration file
- Configuration: the core Configuration file, which contains the content parsed from SQLmapconfig. XML
- MappedStatement: mapping configuration class, which stores the contents parsed by mapper. XML
-
Parse the configuration file: dom4j
- Create class SqlSessionFactoryBuilder: Build (InputSteam in)
- Use DOM4J to parse the configuration file and encapsulate the parsed content into container objects
- Create SqlSessionFactory, produce SqlSession session object (factory mode)
- Create class SqlSessionFactoryBuilder: Build (InputSteam in)
-
Create the SqlSessionFactory interface and implement DefaultSqlSessionFactory
- OpenSession ();
-
Create SqlSession interface and implement DefaultSqlSession
- All database additions and deletions are encapsulated here
- Define a curD operation on a database:
- list()
- get()
- update()
- delete()
-
Create the Executor interface and implementation class DefaultExecutor implementation class
- query(Configuration config,MappedStatement statement,Object… Params) : executes JDBC code
2. Process used by the client
1. Configure the client
pom.xml
<dependency>
<groupId>com.otoomo</groupId>
<artifactId>persistent</artifactId>
<version>1.0 the SNAPSHOT</version>
</dependency>
Copy the code
SqlMapConfiguration.xml
<configuration>
<! -- Database configuration -->
<datasouce>
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
<property name="jdbcUrl" value="jdbc:mysql:///persistent_test? useUnicode=true&characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</datasouce>
<! SQL > select * from 'SQL';
<mapper resource="UserMapper.xml"/>
</configuration>
Copy the code
UserMapper.xml
- The custom persistence framework can locate the specific SQL that needs to be executed based on the rules
statementId
To locate. - the
statememtId
The rules are composed of the following parts:namespace.id
(namespace +”.”+ SQL configuration ID)namespace
: Full path of the package name of UserDaoid
: The unique identifier of the SQL configuration, which is the corresponding method name in the UserDao - In SqlSession, it is assembled by the class name + method name of the DAO object currently executed
statememtId
, and then executed using the JDK dynamic proxy
<mapper namespace="com.otoomo.dao.UserDao">
<! -- Query all users -->
<select id="queryAll" resultType="com.otoomo.pojo.User" parameterType="com.otoomo.pojo.User">
SELECT * FROM user;
</select>
<! -- Get user by ID -->
<select id="getById" resultType="com.otoomo.pojo.User" parameterType="java.lang.Integer">
SELECT * FROM user WHERE id = #{id};
</select>
<select id="getByIdAndName" resultType="com.otoomo.pojo.User" parameterType="java.util.Map">
SELECT * FROM user WHERE id = #{id} AND username = #{username};
</select>
<! Add user -->
<insert id="add" parameterType="com.otoomo.pojo.User">
insert into user (`username`) values(#{username})
</insert>
<! -- Delete user -->
<delete id="delete">
delete from user where id = #{id}
</delete>
<! -- Update user -->
<update id="update" parameterType="com.otoomo.pojo.User">
update user set username=#{username} where id = #{id}
</update>
</mapper>
Copy the code
2. Code application
public class TestApplication {
UserMapper userMapper;
@Before
public void initUserMapper(a) throws ParserException {
// Get the configuration input stream
InputStream inputStream = Resources.getResourceAsSteam("sqlMapConfiguration.xml");
// Get the SQL session
SqlSessionFactory sqlSessionFactory = SqlSessionFactoryBuilder.build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
userMapper = sqlSession.getMapper(UserMapper.class);
}
@Test
public void addUser(a) {
User user = new User();
user.setUsername("zhangsan");
userMapper.add(user);
// Get all users
List<User> users = userMapper.queryAll();
System.out.println(users);
}
@Test
public void update(a) {
int id = 15;
User updateUser = new User();
updateUser.setId(id);
updateUser.setUsername("Zhang");
userMapper.update(updateUser);
// Get the user
User user = userMapper.getById(id);
System.out.println(user);
}
@Test
public void delete(a) {
int delete = userMapper.delete(16);
System.out.println(delete);
}
@Test
public void getByIdAndName(a) {
User user = userMapper.getByIdAndName(17."Tom"); System.out.println(user); }}Copy the code
3. Implementation steps
Maven dependency profile:
<dependencies>
<! -- Database related -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.17</version>
</dependency>
<! -- Connection pool -->
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
<! --END database related -->
<! -- Dom4j XML parsing package -->
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
<! Dom4j uses Xpath to locate elements when using selectNodes, so you need to add JAXEN -->
<dependency>
<groupId>jaxen</groupId>
<artifactId>jaxen</artifactId>
<version>1.1.1</version>
</dependency>
</dependencies>
Copy the code
Create the Resources class
Resources is used to retrieve file configuration information, return an input stream to the caller, and then parse the configuration through DOM4J or other means
public class Resources {
private Resources(a) {}/** * get the resource InputStream */ for the specified path
public static InputStream getResourceAsSteam(String path) {
InputStream resourceInputStream = ClassLoader.getSystemResourceAsStream(path);
returnresourceInputStream; }}Copy the code
2. Create two container classes
MappedStatement
The Sql configuration mapping container class stores the configuration information in mapper.xml. A configuration item is an MappedStatement object
public class MappedStatement {
/** * SQL unique id */
private String id;
/** * Return value type */
private String resultType;
/** * Parameter value type */
private String paramterType;
}
Copy the code
Configuration Core Configuration container class
Database configuration information container; The MappedStatement and DataSource need to be retrieved frequently.
To reduce the passing of multiple parameters, the MappedStatement information can be stored in the core configuration container mappedStatementMap.
All you need to do is pass the Configuration parameter
public class Configuration {
/** * Stores the database configuration information object */
private DataSource dataSource;
/** * SQL configuration mapping Map * key: statementId * value: The encapsulated SQL configuration mapping class * * statementId adds the unique ID identifier of the SQL configuration mapping to the namespace in mapper. XML * statementId = namespace.id */
private Map<String, MappedStatement> mappedStatementMap = new HashMap<>();
}
Copy the code
3. Resolve the core configuration
Dom4j is used to parse the file. The core configuration file is SQLMapConfiguration.xml
Divided into three steps:
- Create the XmlDataSourceParser to parse the database configuration information
- Create an XmlMapperParser to parse mapper.xml
- Create a core XmlConfigurationParser that calls both XmlDataSourceParser and XmlMapperParser
public interface Parser<I> {
<T> T parse(I parseObj) throws ParserException;
}
Copy the code
1. Create the XmlDataSourceParser
Parse the database configuration. Obtain the dataSource of the Configuration container
<datasource>
<property name="driviceClass" value="..."/>
</datasource>
Copy the code
public class XmlDataSourceConfigParser implements Parser<Document> {
private Configuration configuration;
public XmlDataSourceConfigParser(Configuration configuration) {
this.configuration = configuration;
}
@Override
public DataSource parse(Document document) throws ParserException {
try {
//<configuration>
Element rootElement = document.getRootElement();
// Get all property configuration items
List<Element> propertyElements = rootElement.selectNodes("//property");
// Get all property values
Properties properties = new Properties();
propertyElements.forEach(element -> {
String name = element.attributeValue("name");
String value = element.attributeValue("value");
properties.put(name, value);
});
// Obtain the data source through the configuration information
DataSource dataSource = buildDataSource(properties);
configuration.setDataSource(dataSource);
} catch (DataSourceConfigParamException | PropertyVetoException e) {
throw new ParserException(e.getMessage(), e);
}
return null;
}
/** * Build data source **@param properties
* @return
* @throws DataSourceConfigParamException
* @throws PropertyVetoException
*/
private DataSource buildDataSource(Properties properties) throws DataSourceConfigParamException, PropertyVetoException {
// Check whether the database core configuration is valid
checkDataSourcePropertysValidity(properties);
String driverClass = properties.getProperty("driverClass");
String jdbcUrl = properties.getProperty("jdbcUrl");
String username = properties.getProperty("username");
String password = properties.getProperty("password");
//TODO nulls
// Create a database connection pool after obtaining the configuration information
ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
comboPooledDataSource.setDriverClass(driverClass);
comboPooledDataSource.setJdbcUrl(jdbcUrl);
comboPooledDataSource.setUser(username);
comboPooledDataSource.setPassword(password);
//TODO improves more parameter configurations
return comboPooledDataSource;
}
/** * Check whether the database configuration information is valid **@param properties
* @throws DataSourceConfigParamException
*/
private void checkDataSourcePropertysValidity(Properties properties) throws DataSourceConfigParamException {
String driverClass = properties.getProperty("driverClass");
String jdbcUrl = properties.getProperty("jdbcUrl");
String username = properties.getProperty("username");
String password = properties.getProperty("password");
if (StringUtils.isNullOrEmpty(driverClass)) {
throw new DataSourceConfigParamException("driverClass");
}
if (StringUtils.isNullOrEmpty(jdbcUrl)) {
throw new DataSourceConfigParamException("jdbcUrl");
}
if (StringUtils.isNullOrEmpty(username)) {
throw new DataSourceConfigParamException("username");
}
if (StringUtils.isNullOrEmpty(password)) {
throw new DataSourceConfigParamException("password"); }}}Copy the code
2. Create an XmlMapperParser
The mapper. XML file path for parsing and the specific mapper. XML file are saved in the MappedMatement container.
Save the MappedMatement to the mappedStatementMap in the Configuration container
<mapper resource="UserMapper.xml" />.Copy the code
public class XmlMapperParser implements Parser<InputStream> {
private Configuration configuration;
public XmlMapperParser(Configuration configuration) {
this.configuration = configuration;
}
/** * parse mapper files **@param inputStream
* @return* /
public MappedStatement parse(InputStream inputStream) throws ParserException {
try {
Document document = new SAXReader().read(inputStream);
//<mapper>
Element rootElement = document.getRootElement();
/ / select, update, insert, and delete statements
handlerSelectElements(rootElement);
handlerUpdateElements(rootElement);
handlerInsertElements(rootElement);
handlerDeleteElements(rootElement);
} catch (DocumentException e) {
throw new ParserException(e.getMessage(), e);
}
return null;
}
private void handlerSelectElements(Element rootElement) {
handlerElements(rootElement, "select", SqlCommandType.SELECT);
}
private void handlerDeleteElements(Element rootElement) {
handlerElements(rootElement, "delete", SqlCommandType.DELETE);
}
private void handlerInsertElements(Element rootElement) {
handlerElements(rootElement, "insert", SqlCommandType.INSERT);
}
private void handlerUpdateElements(Element rootElement) {
handlerElements(rootElement, "update", SqlCommandType.UPDATE);
}
/** * process SQL configuration **@param rootElement
* @param type
* @param sqlCommandType
*/
private void handlerElements(Element rootElement, String type, SqlCommandType sqlCommandType) {
// Namespace
String namespace = rootElement.attributeValue("namespace");
List<Element> elementList = rootElement.selectNodes("/ /" + type);
elementList.forEach(element -> {
String id = element.attributeValue("id");
String resultType = element.attributeValue("resultType");
String parameterType = element.attributeValue("parameterType");
String sql = element.getTextTrim();
MappedStatement mappedStatement = new MappedStatement(id, resultType, parameterType, sql, sqlCommandType);
configuration.getMappedStatementMap().put(namespace + "."+ id, mappedStatement); }); }}Copy the code
- SqlCommandType is used to record the type of THE SQL mapping
public enum SqlCommandType {
INSERT, / / insert
UPDATE, / / update
DELETE, / / delete
SELECT; / / query
private SqlCommandType(a) {}}Copy the code
3. Create the core XmlConfigurationParser
Use to call XmlDataSourceParser, XmlMapperParser two parsers, get DataSource and mappedStatementMap data.
<configuration>
<datasource >
<property name="" value=""/>
</datasource>
<mapper resource=""/>
</configuration>
Copy the code
public class XmlConfigurationParser implements Parser<InputStream> {
private Configuration configuration;
public XmlConfigurationParser(a) {
configuration = new Configuration();
}
@Override
public Configuration parse(InputStream inputStream) throws ParserException {
try {
Document document = new SAXReader().read(inputStream);
/*
解析数据库配置
*/
Parser xmlDataSourceConfigParser = new XmlDataSourceConfigParser(configuration);
xmlDataSourceConfigParser.parse(document);
/* Parse the mapper configuration */
Parser xmlMapperParser = new XmlMapperParser(configuration);
/ / < configuration > root element
Element rootElement = document.getRootElement();
// Get all mapper elements
List<Element> mapperElements = rootElement.selectNodes("//mapper");
for (Element mapperPathElement : mapperElements) {
// Get the specific mapper. XML file path
String mapperPath = mapperPathElement.attributeValue("resource");
InputStream mapperInputStream = Resources.getResourceAsSteam(mapperPath);
// Start parsing the mapper.xml file
xmlMapperParser.parse(mapperInputStream);
}
return configuration;
} catch (DocumentException e) {
throw newParserException(e.getMessage(), e); }}}Copy the code
4. Create a SqlSession
Divided into three steps:
- Create SqlSession interface and implement DefaultSqlSession
- Create the SqlSessionFactory factory class to produce SQLSessions
- Create the SqlSessionFactoryBuilder to create the SqlSessionFactory
The Executor class is involved here. This will be implemented in step 5, where we first implement SqlSession related creation
1.SqlSession
SqlSession is used to perform JDBC CURD operations.
public interface SqlSession {
<T> List<T> query(String statementId, Object... params) throws Exception;
<T> T get(String statementId, Object... params) throws Exception;
int update(String statementId, Object... params) throws Exception;
int delete(String statementId, Object... params) throws Exception;
int insert(String statementId, Object... params) throws Exception;
/** * To reduce statementId hardcoding and code duplication of SqlSession. * <p> * Use dynamic proxy methods, using JDK dynamic proxy, to map the execution of the corresponding query, selectOne,update, and other interfaces * *@param clazz
* @param <T>
* @return* /
<T> T getMapper(Class
clazz);
}
Copy the code
public class DefaultSqlSession implements SqlSession {
private Configuration configuration;
public DefaultSqlSession(Configuration configuration) {
this.configuration = configuration;
}
@Override
public <T> List<T> query(String statementId, Object... params) throws Exception {
DefaultExecutor defaultExecutor = new DefaultExecutor();
MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);
return defaultExecutor.query(configuration, mappedStatement, params);
}
@Override
public <T> T get(String statementId, Object... params) throws Exception {
// Query the list interface to get the first value returned
List<Object> objectList = query(statementId, params);
if (objectList.size() == 1) {
return (T) objectList.get(0);
}
if (objectList.size() > 1) {
throw new RuntimeException("Multiple Return Objects Error");
}
return null;
}
@Override
public int update(String statementId, Object... params) throws Exception {
DefaultExecutor defaultExecutor = new DefaultExecutor();
MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);
return defaultExecutor.update(configuration, mappedStatement, params);
}
@Override
public int delete(String statementId, Object... params) throws Exception {
return update(statementId, params);
}
@Override
public int insert(String statementId, Object... params) throws Exception {
return update(statementId, params);
}
@Override
public <T> T getMapper(Class
clazz) {
// Use the JDK's dynamic proxy technology
Object handler = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(), new Class[]{clazz}, (proxy, method, args) -> {
// The underlying implementation is JDBC
StatementId, params,
// The syntax of statementId is namespace.id.
// Therefore, the naming convention of mapper. XML is as follows: Namespace is the name of Dao's full-path class, and ID is the name of Dao's function
//id
String id = method.getName();
/ / the name of the class
String namespace = method.getDeclaringClass().getName();
String statementId = namespace + "."+ id; Class<? > returnType = method.getReturnType();// If the number of arguments is greater than 1, it is converted to a map object and passed as the value of the first argument
int parameterCount = method.getParameterCount();
if (parameterCount > 1) {
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
Map<String, Object> paramsMap = new HashMap<>();
int i = 0;
for (Annotation[] parameterAnnotation : parameterAnnotations) {
for (Annotation annotation : parameterAnnotation) {
if (annotation instanceof Param) {
Param param = (Param) annotation;
String value = param.value();
paramsMap.put(value, args[i]);
i++;
}
}
}
args[0] = paramsMap;
}
MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);
SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();
switch (sqlCommandType) {
case SELECT:
boolean returnMany = returnMany(returnType);
if (returnMany) {
// set query
return query(statementId, args);
}
// Single query
return get(statementId, args);
case INSERT:
return insert(statementId, args);
case UPDATE:
return update(statementId, args);
case DELETE:
return delete(statementId, args);
default:
throw new RuntimeException("Sql command type error"); }});return (T) handler;
}
/** * Determine whether the return type is a collection or an array@param returnType
* @return* /
private boolean returnMany(Class
returnType) {
boolean isCollection = Collection.class.isAssignableFrom(returnType);
boolean isArray = returnType.isArray();
returnisCollection || isArray; }}Copy the code
-
The Param annotation class is involved
The parameter name used to specify the input parameter. If you specify a name for a multi-parameter method, you will not get the #{username} KEY in mapper. XML
@Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER}) public @interface Param { String value(a); } Copy the code
Chestnut:
public int select(@Param("username") String username,@Param("status") Integer status) Copy the code
2. Create a SqlSessionFactory
Factory schema class for producing SQLSessions
public interface SqlSessionFactory {
SqlSession openSession(a);
}
Copy the code
public class DefaultSqlSessionFactory implements SqlSessionFactory {
private Configuration configuration;
public DefaultSqlSessionFactory(Configuration configuration) {
this.configuration = configuration;
}
@Override
public SqlSession openSession(a) {
DefaultSqlSession sqlSession = new DefaultSqlSession(configuration);
returnsqlSession; }}Copy the code
3.SqlSessionFactoryBuilder
public class SqlSessionFactoryBuilder {
/** * Create SqlSessionFactory ** by configuring the input stream@author modongning
* @date 24/09/2020 10:51 PM
*/
public static SqlSessionFactory build(InputStream inputStream) throws ParserException {
/* 1. Use dom4j to parse the Configuration file and save it in the Configuration container */
Parser configurationParser = new XmlConfigurationParser();
Configuration configuration = (Configuration) configurationParser.parse(inputStream);
/* Create SqlSessionFactory to implement DefaultSqlSessionFactory. Since the configuration information is always needed later, the */ is passed in using the parameter constructor
SqlSessionFactory sqlSessionFactory = new DefaultSqlSessionFactory(configuration);
returnsqlSessionFactory; }}Copy the code
5. Create an SQL Executor
1. Implementation
/** * SQL execution */
public interface Executor {
/** ** **@param configuration
* @param mappedStatement
* @param params
* @return
* @throws Exception
*/
int count(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception;
/** * insert operation **@param configuration
* @param mappedStatement
* @param params
* @return* /
int insert(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception;
/** * Delete operation **@param configuration
* @param mappedStatement
* @param params
* @return
* @throws Exception
*/
int delete(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception;
/** * Update operation **@param configuration
* @param mappedStatement
* @param params
* @return
* @throws Exception
*/
int update(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception;
/** * Perform the query operation **@paramConfiguration Core configuration container *@paramMappedStatement SQL configuration mapping *@paramParams parameters *@param <T>
* @return* /
<T> List<T> query(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception;
}
Copy the code
/** * Default implementation of the SQL executor */
public class DefaultExecutor implements Executor {
@Override
public int count(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception {
return update(configuration, mappedStatement, params);
}
@Override
public int insert(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception {
return update(configuration, mappedStatement, params);
}
@Override
public int delete(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception {
return update(configuration, mappedStatement, params);
}
@Override
public int update(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception {
//TODO inserts, deletes, and statistics all return numeric results. All are performed using the update operation
return (int) executor(configuration, mappedStatement, params);
}
@Override
public <T> List<T> query(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception {
ResultSet resultSet = (ResultSet) executor(configuration, mappedStatement, params);
//6. Encapsulate the return valueString resultType = mappedStatement.getResultType(); Class<? > resultTypeClass = getClassBy(resultType); List<Object> resultList =new ArrayList<>();
while (resultSet.next()) {
// Data object
Object o = resultTypeClass.newInstance();
// The metadata object contains the field name of the data
ResultSetMetaData metaData = resultSet.getMetaData();
int columnCount = metaData.getColumnCount();
for (int i = 1; i <= columnCount; i++) {
/ / the field name
String columnName = metaData.getColumnName(i);
/ / field values
Object value = resultSet.getObject(columnName);
// Once you have the object field name and value, use reflection or introspection to set the corresponding value of the object
// Get the attribute description of the columnName of the resultTypeClass class
//TODO statistical statements such as Select count(*) return a ResultSet. Determine whether a resultType is an object based on the return type of the resultType.
try {
PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, resultTypeClass);
// Get the write method
Method writeMethod = propertyDescriptor.getWriteMethod();
// Set the value of the object field through the write method of the field
writeMethod.invoke(o, value);
} catch (IntrospectionException e) {
// If there is no corresponding field, ignore it
continue;
}
}
resultList.add(o);
}
return (List<T>) resultList;
}
private Object executor(Configuration configuration, MappedStatement mappedStatement, Object... params) throws SQLException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
//1. Obtain the database connection
Connection connection = configuration.getDataSource().getConnection();
SELECT * FROM user WHERE id = #{id}; * /
String mappedStatementSql = mappedStatement.getSql();
SELECT * FROM user WHERE id =?
BoundSql boundSql = getBoundSql(mappedStatementSql);
//4. Set parameters
// Preprocess the object
PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSqlText());
String parameterType = mappedStatement.getParameterType();
// Get the type of the parameter classClass<? > parameterTypeClass = getClassBy(parameterType); List<ParameterMapping> parameterMappingList = boundSql.getParameterMappingList();for (int i = 0; i < parameterMappingList.size(); i++) {
//TODO adds more parameter judgment logic later
Object param = params[0];
if (null == parameterTypeClass
|| (parameterTypeClass.isAssignableFrom(Integer.class)
|| parameterTypeClass.isAssignableFrom(Long.class)
|| parameterTypeClass.isAssignableFrom(Short.class)
|| parameterTypeClass.isAssignableFrom(Number.class)
|| parameterTypeClass.isAssignableFrom(Double.class)
|| parameterTypeClass.isAssignableFrom(Float.class)
|| parameterTypeClass.isAssignableFrom(Character.class)
|| parameterTypeClass.isAssignableFrom(Boolean.class)
|| parameterTypeClass.isAssignableFrom(Byte.class))
) {
//TODO needs to be improved. It can be stored in Map according to the parameter name by judging the type. The parameter name is =value, and it can be treated as an object
preparedStatement.setObject(i + 1, param);
continue;
}
ParameterMapping parameterMapping = parameterMappingList.get(i);
String content = parameterMapping.getContent();
Object value = null;
if (param instanceof Map) {
value = ((Map) param).get(content);
} else {
// Get the field of the content name in the parameter by reflection
Field contentField = parameterTypeClass.getDeclaredField(content);
contentField.setAccessible(true);
// Get the parameter value from the input object
// Get the value of the object content field from the object with argument 0
value = contentField.get(param);
}
preparedStatement.setObject(i + 1, value);
}
/ / 5. Execute SQL
SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();
if (SqlCommandType.SELECT.equals(sqlCommandType)) {
return preparedStatement.executeQuery();
}
return preparedStatement.executeUpdate();
}
privateClass<? > getClassBy(String parameterType)throws ClassNotFoundException {
if (null! = parameterType) {return Class.forName(parameterType);
}
return null;
}
/** * use #{} with? 2. Store the parsed value of #{}, which is then reflected to obtain the parameter value * *@param mappedStatementSql
* @return* /
private BoundSql getBoundSql(String mappedStatementSql) {
// The tag handler class configures the tag parser to handle placeholders.
ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler();
GenericTokenParser genericTokenParser = new GenericTokenParser("# {"."}", parameterMappingTokenHandler);
SQL: SELECT * FROM user WHERE id =?
String sql = genericTokenParser.parse(mappedStatementSql);
//#{} The parsed parameter name
List<ParameterMapping> parameterMappings = parameterMappingTokenHandler.getParameterMappings();
return newBoundSql(sql, parameterMappings); }}Copy the code
3. Related classes to be used by Executor
1.BoundSql
-
/** * Sql configurates the configuration of the mapping class and preprocesses the Sql after parsing and parameter name container */ public class BoundSql { /** * preprocessing SQL */ private String sqlText; /** * List of parsed SQL parameter names */ private List<ParameterMapping> parameterMappingList; public BoundSql(String sqlText, List<ParameterMapping> parameterMappingList) { this.sqlText = sqlText; this.parameterMappingList = parameterMappingList; }}Copy the code
2.ParameterMapping
Store the parsed parameter names of #{}
public class ParameterMapping {
public ParameterMapping(String content) {
this.content = content;
}
/**
* 参数名称
*/
private String content;
}
Copy the code
3.ParameterMappingTokenHandler
Select * from #{username} where username = ‘username’;
public interface TokenHandler {
String handleToken(String content);
}
Copy the code
/** * stores values between SQL statement placeholders #{} and returns the replacement symbol "?" Parametermapping. content is "useranme" */
public class ParameterMappingTokenHandler implements TokenHandler {
private List<ParameterMapping> parameterMappings = new ArrayList<ParameterMapping>();
// context is the parameter name #{id} #{username}
public String handleToken(String content) {
parameterMappings.add(buildParameterMapping(content));
return "?";
}
private ParameterMapping buildParameterMapping(String content) {
ParameterMapping parameterMapping = new ParameterMapping(content);
return parameterMapping;
}
public List<ParameterMapping> getParameterMappings(a) {
return parameterMappings;
}
public void setParameterMappings(List<ParameterMapping> parameterMappings) {
this.parameterMappings = parameterMappings; }}Copy the code
4.GenericTokenParser
General purpose symbol parser, used to parse placeholders for #{}
public class GenericTokenParser {
private final String openToken; // Start the tag
private final String closeToken; // End tag
private final TokenHandler handler; // Mark the handler
public GenericTokenParser(String openToken, String closeToken, TokenHandler handler) {
this.openToken = openToken;
this.closeToken = closeToken;
this.handler = handler;
}
/** * parse ${} and #{} *@param text
* @return* This method mainly realizes the configuration file, script and other fragments of placeholder parsing, processing work, and return the final data needed. * Where parsing is done by this method and processing is done by the handler's handleToken() method */
public String parse(String text) {
// Validate argument problem, if null, return empty string.
if (text == null || text.isEmpty()) {
return "";
}
// If the start tag is not included, the default is not a placeholder. If the start tag is not included, the default is not placeholder.
int start = text.indexOf(openToken, 0);
if (start == -1) {
return text;
}
// Convert text to character array SRC, and define the default offset=0, store the variable builder that eventually needs to return a string,
// text specifies the variable name corresponding to the placeholder in the variable expression. Determine if start is greater than -1(that is, if openToken exists in text) and if so, execute the following code
char[] src = text.toCharArray();
int offset = 0;
final StringBuilder builder = new StringBuilder();
StringBuilder expression = null;
while (start > -1) {
If there is an escape character before the start token, it will not be processed as openToken, otherwise it will continue to be processed
if (start > 0 && src[start - 1] = ='\ \') {
builder.append(src, offset, start - offset - 1).append(openToken);
offset = start + openToken.length();
} else {
// Reset the expression variable to avoid null Pointers or old data interference.
if (expression == null) {
expression = new StringBuilder();
} else {
expression.setLength(0);
}
builder.append(src, offset, start - offset);
offset = start + openToken.length();
int end = text.indexOf(closeToken, offset);
while (end > -1) {//// if an end flag exists
if (end > offset && src[end - 1] = ='\ \') {// If the end tag is preceded by an escape character
// this close token is escaped. remove the backslash and continue.
expression.append(src, offset, end - offset - 1).append(closeToken);
offset = end + closeToken.length();
end = text.indexOf(closeToken, offset);
} else {// There are no escape characters that need to be processed as arguments
expression.append(src, offset, end - offset);
offset = end + closeToken.length();
break; }}if (end == -1) {
// close token was not found.
builder.append(src, start, src.length - start);
offset = src.length;
} else {
// The parameter is processed according to the key (expression) of the parameter. As a placeholder
builder.append(handler.handleToken(expression.toString()));
offset = end + closeToken.length();
}
}
start = text.indexOf(openToken, offset);
}
if (offset < src.length) {
builder.append(src, offset, src.length - offset);
}
returnbuilder.toString(); }}Copy the code