Analyze JDBC operation problems
public static void main(String[] args) throws Exception {
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
// Load the database driver
Class.forName("com.mysql.jdbc.Driver");
// Get the database link from the driver management class
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis? characterEncoding=utf-8"."root"."root");
// Define SQL statements? Represents the placeholder S
String sql = "select * from user where username = ?";
// Get preprocessing
preparedStatement = connection.prepareStatement(sql);
The first parameter is the sequence number (starting from 1) of the parameter in the SQL statement, and the second parameter is the value of the parameter
preparedStatement.setString(1."tom");
// Issue SQL to the database to execute the query and query the result set
resultSet = preparedStatement.executeQuery();
User user = new User();
// Iterate over the query result set
while (resultSet.next()) {
int id = resultSet.getInt("id");
String username = resultSet.getString("username");
/ / packaging
user.setId(id);
user.setUsername(username);
}
System.out.println(user);
} catch (Exception e) {
e.printStackTrace();
} finally {
// Release resources
if(resultSet ! =null) {
try {
resultSet.close();
} catch(SQLException e) { e.printStackTrace(); }}if(preparedStatement ! =null) {
try {
preparedStatement.close();
} catch(SQLException e) { e.printStackTrace(); }}if(connection ! =null) {
try {
connection.close();
} catch(SQLException e) { e.printStackTrace(); }}}}Copy the code
Summary of JDBC issues:
-
The frequent creation and release of database connections waste system resources, which affects system performance.
-
Sql statements are hardcoded in the code, which makes the code difficult to maintain. Therefore, the Sql may change greatly in actual applications, and the Java code needs to be changed.
-
Hard coding exists in transferring parameters to possession bit symbols in preparedStatement, because the WHERE conditions of SQL statements are not necessarily, which may be more or less. Modifying SQL requires modifying codes, making the system difficult to maintain.
-
There is hard coding (query column name) for result set parsing, SQL changes lead to parsing code changes, the system is not easy to maintain, if the database records can be encapsulated as POJO object parsing is more convenient.
Problem Solution
- ① Initialize connection resources using the database connection pool
- ② Extract SQL statements into XML configuration files
- ③ Use reflection, introspection and other underlying technologies to automatically map attributes and fields between entities and tables
Custom framework design
Using the
Provide core configuration files:
-
Sqlmapconfig. XML: stores data source information and introduces mapper. XML
-
Mapper. XML: configuration file information of the SQL statement
Frame side
1. Read the configuration file
After reading, it exists in the form of a stream. We cannot store the configuration information read in the form of a stream in the memory. It is not easy to operate
Create javabeans for storage
-
(1) Configuration: Stores the basic database information and the unique identifier of Map and Mapper: namespace + “.” + ID
-
(2) MappedStatement: SQL statement, Statement type, input parameter Java type, output parameter Java type
2. Parse the configuration file
SqlSessionFactory Build () :
-
First: use DOM4J to parse the Configuration file and encapsulate the parsed content in Configuration and MappedStatement
-
Create an implementation class DefaultSqlSession for SqlSessionFactory
Create SqlSessionFactory
Method: openSession() : Obtain the implementation class instance object of the sqlSession interface
4. Create sqlSession interface and implementation class: encapsulates CRUD methods
-
SelectList (String statementId,Object Param) : Query all
-
SelectOne (String statementId,Object Param) : Queries a single Object
Specific implementation: encapsulation JDBC to complete the database table query operation.
Design patterns involved: Builder design pattern, factory pattern, agent pattern
Custom framework implementation
Using the
Create a configuration profile in the user side project
Create sqlmapconfig.xml as follows:
<configuration>
<! -- Database configuration information -->
<dataSource>
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql:///zdy_mybatis"></property>
<property name="username" value="root"></property>
<property name="password" value="root"></property>
</dataSource>
<! Where mapper.xml is stored -->
<mapper resource="UserMapper.xml"></mapper>
</configuration>
Copy the code
Mapper. XML is as follows:
<mapper namespace="com.lagou.dao.IUserDao">
<! StatementId --> statementId--> statementId
<select id="findAll" resultType="com.lagou.pojo.User" >
select * from user
</select>
<! -- User user = new User() user.setId(1); user.setUsername("zhangsan") -->
<select id="findByCondition" resultType="com.lagou.pojo.User" paramterType="com.lagou.pojo.User">
select * from user where id = #{id} and username = #{username}
</select>
</mapper>
Copy the code
The User entity is as follows:
public class User {
private Integer id;
private String username;
public Integer getId(a) {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername(a) {
return username;
}
public void setUsername(String username) {
this.username = username;
}
@Override
public String toString(a) {
return "User{" +
"id=" + id +
", username='" + username + ' ''+'}'; }}Copy the code
Frame side
Create a Maven subproject and import the required dependency coordinates.
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.lagou</groupId>
<artifactId>IPersistence</artifactId>
<version>1.0 the SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.encoding>UTF-8</maven.compiler.encoding>
<java.version>1.8</java.version>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.17</version>
</dependency>
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.12</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
</dependency>
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
<dependency>
<groupId>jaxen</groupId>
<artifactId>jaxen</artifactId>
<version>1.1.6</version>
</dependency>
</dependencies>
</project>
Copy the code
The Configuration class encapsulates the information of the sqlmapconfig. XML and mapper. XML Configuration files
/** * Configuration: stores the contents parsed from the sqlmapconfig. XML Configuration file * MappedStatement: stores the contents parsed from the mapper. XML Configuration file */
public class Configuration {
private DataSource dataSource;
/** * key: Statementid value: encapsulates the mappedStatement object * the unique identifier of the SQL: namesapec. id: Statementid * namesapce is used to distinguish usermapper. XML from productmapper. XML * ID is the id of each mapper. XML */
Map<String, MappedStatement> mappedStatementMap = new HashMap<>();
public DataSource getDataSource(a) {
return dataSource;
}
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
public Map<String, MappedStatement> getMappedStatementMap(a) {
return mappedStatementMap;
}
public void setMappedStatementMap(Map<String, MappedStatement> mappedStatementMap) {
this.mappedStatementMap = mappedStatementMap; }}Copy the code
MappedStatement class: stores the contents parsed from the mapper. XML configuration file
/** * The mapper. XML configuration file parses the contents * equivalent to a tag */
public class MappedStatement {
private String id;
// Return value type
private String resultType;
// Parameter value type
private String paramterType;
/ / SQL statements
private String sql;
public String getId(a) {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getResultType(a) {
return resultType;
}
public void setResultType(String resultType) {
this.resultType = resultType;
}
public String getParamterType(a) {
return paramterType;
}
public void setParamterType(String paramterType) {
this.paramterType = paramterType;
}
public String getSql(a) {
return sql;
}
public void setSql(String sql) {
this.sql = sql; }}Copy the code
Resources class: Loads the configuration file as a byte input stream based on its path and stores it in memory
public class Resources {
// Based on the path of the configuration file, load the configuration file into a byte input stream and store it in memory
public static InputStream getResourceAsSteam(String path){
InputStream resourceAsStream = Resources.class.getClassLoader().getResourceAsStream(path);
returnresourceAsStream; }}Copy the code
The SqlSessionFactoryBuilder is class:
-
Use DOM4J to parse the Configuration file and encapsulate the parsed content in Configuration
-
Create sqlSessionFactory object: Factory class: produce sqlSession: session object
public class SqlSessionFactoryBuilder {
public SqlSessionFactory build(InputStream in) throws DocumentException, PropertyVetoException {
// First: use dom4j to parse the Configuration file and encapsulate the parsed content in Configuration
XMLConfigBuilder xmlConfigBuilder = new XMLConfigBuilder();
Configuration configuration = xmlConfigBuilder.parseConfig(in);
// Create sqlSessionFactory: factory class: produce sqlSession: session object
DefaultSqlSessionFactory defaultSqlSessionFactory = new DefaultSqlSessionFactory(configuration);
returndefaultSqlSessionFactory; }}Copy the code
XMLConfigBuilder class: Uses dom4J to parse the core Configuration file SQLmapconfig. XML and encapsulate the Configuration
public class XMLConfigBuilder {
private Configuration configuration;
public XMLConfigBuilder(a) {
this.configuration = new Configuration();
}
/** * This method uses dom4j to parse the Configuration file and encapsulate the Configuration */
public Configuration parseConfig(InputStream inputStream) throws DocumentException, PropertyVetoException {
Document document = new SAXReader().read(inputStream);
// Get the object, which is the
tag
Element rootElement = document.getRootElement();
// //property is an xpath, indicating that 3 searches for all
tag elements in the corresponding root node
List<Element> list = rootElement.selectNodes("//property");
Properties properties = new Properties();
for (Element element : list) {
// Fetch the value of the name attribute
String name = element.attributeValue("name");
// Fetch the value of the value attribute
String value = element.attributeValue("value");
properties.setProperty(name,value);
}
// assemble DataSource DataSource information
ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
comboPooledDataSource.setDriverClass(properties.getProperty("driverClass"));
comboPooledDataSource.setJdbcUrl(properties.getProperty("jdbcUrl"));
comboPooledDataSource.setUser(properties.getProperty("username"));
comboPooledDataSource.setPassword(properties.getProperty("password"));
configuration.setDataSource(comboPooledDataSource);
//mapper. XML parsing: take the path - byte input stream -dom4j parsing
List<Element> mapperList = rootElement.selectNodes("//mapper");
for (Element element : mapperList) {
String mapperPath = element.attributeValue("resource");
InputStream resourceAsSteam = Resources.getResourceAsSteam(mapperPath);
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(configuration);
xmlMapperBuilder.parse(resourceAsSteam);
}
returnconfiguration; }}Copy the code
XMLMapperBuilder class: Use dom4J to parse and encapsulate the contents of the mapper.xml configuration file
public class XMLMapperBuilder {
private Configuration configuration;
public XMLMapperBuilder(Configuration configuration) {
this.configuration =configuration;
}
public void parse(InputStream inputStream) throws DocumentException {
Document document = new SAXReader().read(inputStream);
Element rootElement = document.getRootElement();
String namespace = rootElement.attributeValue("namespace");
// Corresponds to the
List<Element> list = rootElement.selectNodes("//select");
for (Element element : list) {
String id = element.attributeValue("id");
String resultType = element.attributeValue("resultType");
String paramterType = element.attributeValue("paramterType");
String sqlText = element.getTextTrim();
MappedStatement mappedStatement = new MappedStatement();
mappedStatement.setId(id);
mappedStatement.setResultType(resultType);
mappedStatement.setParamterType(paramterType);
mappedStatement.setSql(sqlText);
/ / is statementid
String key = namespace+"."+id; configuration.getMappedStatementMap().put(key,mappedStatement); }}}Copy the code
SqlSessionFactory interface and DefaultSqlSessionFactory implementation classes: used to create SqlSession sessions
public interface SqlSessionFactory {
public 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) {
return newDefaultSqlSession(configuration); }}Copy the code
SqlSession interface and DefaultSqlSession implementation class: session, used to operate the database
public interface SqlSession {
// Query all
public <E> List<E> selectList(String statementid,Object... params) throws Exception;
// Query a single item based on the condition
public <T> T selectOne(String statementid,Object... params) throws Exception;
// Generate the proxy implementation class for the Dao interface
public <T> T getMapper(Class
mapperClass);
}
Copy the code
public class DefaultSqlSession implements SqlSession {
private Configuration configuration;
public DefaultSqlSession(Configuration configuration) {
this.configuration = configuration;
}
@Override
public <E> List<E> selectList(String statementid, Object... params) throws Exception {
// I'm going to call the Query method in simpleExecutor
simpleExecutor simpleExecutor = new simpleExecutor();
MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementid);
List<Object> list = simpleExecutor.query(configuration, mappedStatement, params);
return (List<E>) list;
}
@Override
public <T> T selectOne(String statementid, Object... params) throws Exception {
List<Object> objects = selectList(statementid, params);
if(objects.size()==1) {return (T) objects.get(0);
}else {
throw new RuntimeException("Query result is empty or too many results are returned"); }}}Copy the code
Executor Executor: Used to actually execute SQL statements.
public interface Executor {
public <E> List<E> query(Configuration configuration,MappedStatement mappedStatement,Object... params) throws Exception;
}
Copy the code
public class simpleExecutor implements Executor {
@Override //user
public <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception {
// 1. Register the driver and obtain the connection
Connection connection = configuration.getDataSource().getConnection();
Select * from user where id = #{id} and username = #{username}
Select * from user where id =? and username = ? The value in #{} needs to be parsed as well
String sql = mappedStatement.getSql();
BoundSql boundSql = getBoundSql(sql);
// 3. Get the prepared object: preparedStatement
PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSqlText());
// 4. Set parameters
// Get the full path of the parameterString paramterType = mappedStatement.getParamterType(); Class<? > paramtertypeClass = getClassType(paramterType);// Get the set of attribute names in #{}
List<ParameterMapping> parameterMappingList = boundSql.getParameterMappingList();
for (int i = 0; i < parameterMappingList.size(); i++) {
ParameterMapping parameterMapping = parameterMappingList.get(i);
// the id in #{id}
String content = parameterMapping.getContent();
// reflection gets the property object
Field declaredField = paramtertypeClass.getDeclaredField(content);
// Violent access prevention property is private, so access cannot be accessed
declaredField.setAccessible(true);
//params[0] is the user parameter object, and the resulting o is the value corresponding to the placeholder in #{}
Object o = declaredField.get(params[0]);
// Note that the subscript of the Settings parameter starts at 1
preparedStatement.setObject(i + 1, o);
}
// 5. Execute SQL
ResultSet resultSet = preparedStatement.executeQuery();
// Get the full path of the entity in resultTypeString resultType = mappedStatement.getResultType(); Class<? > resultTypeClass = getClassType(resultType); ArrayList<Object> objects =new ArrayList<>();
// 6. Encapsulate the return result set
while (resultSet.next()) {
// Use reflection to create entity-class objects
Object o = resultTypeClass.newInstance();
/ / metadata
ResultSetMetaData metaData = resultSet.getMetaData();
for (int i = 1; i <= metaData.getColumnCount(); i++) {
/ / the field name
String columnName = metaData.getColumnName(i);
// The value of the field
Object value = resultSet.getObject(columnName);
// Use reflection or introspection to complete the encapsulation according to the corresponding relationship between database tables and entities
The /** * PropertyDescriptor class is a class in the introspection library * that generates accessor methods */ based on the columnName property in the resultTypeClass class
PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, resultTypeClass);
// Get the generated write method, which is equivalent to the setId or setUsername methods of the User class
Method writeMethod = propertyDescriptor.getWriteMethod();
// Execute the write method
writeMethod.invoke(o, value);
}
objects.add(o);
}
return (List<E>) objects;
}
/** * Create an entity Class object * based on the entity Class path using reflection@paramParamterType encapsulates the full path of the entity class */
privateClass<? > getClassType(String paramterType)throws ClassNotFoundException {
if(paramterType ! =null) { Class<? > aClass = Class.forName(paramterType);return aClass;
}
return null;
}
#{/** *} #{/** *} 2. Parse the value inside #{} to store */
private BoundSql getBoundSql(String sql) {
// Tag handler class: configure tag parsers to handle placeholders (#{})
ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler();
GenericTokenParser genericTokenParser = new GenericTokenParser("# {"."}", parameterMappingTokenHandler);
#{id} becomes?
String parseSql = genericTokenParser.parse(sql);
//#{} parses the parameter name
List<ParameterMapping> parameterMappings = parameterMappingTokenHandler.getParameterMappings();
BoundSql boundSql = new BoundSql(parseSql, parameterMappings);
returnboundSql; }}Copy the code
BoundSql class: used to encapsulate SQL and placeholder property names in #{} in the mapper.xml configuration file.
public class BoundSql {
private String sqlText; // Parsed SQL
// Store the object attribute names in #{}
private List<ParameterMapping> parameterMappingList = new ArrayList<>();
public BoundSql(String sqlText, List<ParameterMapping> parameterMappingList) {
this.sqlText = sqlText;
this.parameterMappingList = parameterMappingList;
}
public String getSqlText(a) {
return sqlText;
}
public void setSqlText(String sqlText) {
this.sqlText = sqlText;
}
public List<ParameterMapping> getParameterMappingList(a) {
return parameterMappingList;
}
public void setParameterMappingList(List<ParameterMapping> parameterMappingList) {
this.parameterMappingList = parameterMappingList; }}Copy the code
Use side test classes
public class IPersistenceTest {
@Test
public void test(a) throws Exception {
InputStream resourceAsSteam = Resources.getResourceAsSteam("sqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsSteam);
SqlSession sqlSession = sqlSessionFactory.openSession();
/ / call
User user = new User();
user.setId(1);
user.setUsername("Zhang");
User user2 = sqlSession.selectOne("user.selectOne", user);
System.out.println(user2);
List<User> users = sqlSession.selectList("user.selectList"); }}Copy the code
Custom framework optimization
With our custom framework above, we have solved some of the problems associated with JDBC operating databases, such as frequently creating and releasing database connections, hard coding, and manually encapsulating return result sets, but now we move on to the custom framework code we just completed. Are there any problems?
The questions are as follows:
-
Dao implementation class has duplicate code, the entire operation process template is repeated (create SQLSession, call SQLSession method, close SQLSession)
-
The DAO implementation class is hard-coded. When the SQLSession method is called, the ID of statement is hard-coded
Solution: Use proxy mode to create proxy objects for the interface.
The test class code is modified as follows:
public class IPersistenceTest {
@Test
public void test(a) throws Exception {
InputStream resourceAsSteam = Resources.getResourceAsSteam("sqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsSteam);
SqlSession sqlSession = sqlSessionFactory.openSession();
/ / call
User user = new User();
user.setId(1);
user.setUsername("Zhang");
IUserDao userDao = sqlSession.getMapper(IUserDao.class);
List<User> all = userDao.findAll();
for(User user1 : all) { System.out.println(user1); }}}Copy the code
Add methods to sqlSession
public interface SqlSession {
// Generate the proxy implementation class for the Dao interface
public <T> T getMapper(Class
mapperClass);
}
Copy the code
Add the getMapper method in DefaultSqlSession to create proxy objects for the DAO layer
public class DefaultSqlSession implements SqlSession {
private Configuration configuration;
public DefaultSqlSession(Configuration configuration) {
this.configuration = configuration;
}
@Override
public <E> List<E> selectList(String statementid, Object... params) throws Exception {
// I'm going to call the Query method in simpleExecutor
simpleExecutor simpleExecutor = new simpleExecutor();
MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementid);
List<Object> list = simpleExecutor.query(configuration, mappedStatement, params);
return (List<E>) list;
}
@Override
public <T> T selectOne(String statementid, Object... params) throws Exception {
List<Object> objects = selectList(statementid, params);
if(objects.size()==1) {return (T) objects.get(0);
}else {
throw new RuntimeException("Query result is empty or too many results are returned"); }}@Override
public <T> T getMapper(Class
mapperClass) {
// Use the JDK dynamic proxy to generate proxy objects for the Dao interface and return
Object proxyInstance = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(), new Class[]{mapperClass}, new InvocationHandler() {
/ * * * *@paramProxy Reference of the proxy object *@paramMethod A reference to the currently invoked method@paramArgs The argument * passed by the current calling method@return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Call selctList or selectOne, depending on the case
Parameter 1: Statmentid: the unique identifier of the SQL statement: namespace.id= The fully qualified name of the interface. The method name
// The method name is findAll
String methodName = method.getName();
// Get com.lagou.dao.iuserDAO
String className = method.getDeclaringClass().getName();
/ / com. Namely lagou. Dao. IUserDao. findAll
String statementId = className+"."+methodName;
// Prepare parameter 2: params:args
// Gets the return value type of the called method
Type genericReturnType = method.getGenericReturnType();
List
= List
= List
if(genericReturnType instanceof ParameterizedType){
List<Object> objects = selectList(statementId, args);
return objects;
}
returnselectOne(statementId,args); }});return(T) proxyInstance; }}Copy the code