Pelajar yang mempelajari Java pasti telah didedahkan dengan jdbc Mari kita semak operasi jdbc yang kita hadapi semasa tempoh pemula
Berikut. kod menyambung ke pangkalan data untuk menanyakan pengguna Maklumat jadual, medan jadual pengguna ialah id pengguna dan nama pengguna nama pengguna masing-masing.
Connection connection = null; PreparedStatement preparedStatement = null; ResultSet resultSet = null; User user = new User(); try { // 加载数据库驱动 //Class.forName("com.mysql.jdbc.Driver"); Class.forName("com.mysql.cj.jdbc.Driver"); // 通过驱动管理类获取数据库链接 connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8", "root", "mimashi3124"); // 定义sql语句?表示占位符 String sql = "select * from user where username = ?"; // 获取预处理statement preparedStatement = connection.prepareStatement(sql); // 设置参数,第⼀个参数为sql语句中参数的序号(从1开始),第⼆个参数为设置的参数值 preparedStatement.setString(1, "盖伦"); // 向数据库发出sql执⾏查询,查询出结果集 resultSet = preparedStatement.executeQuery(); // 遍历查询结果集 while (resultSet.next()) { int id = resultSet.getInt("id"); String username = resultSet.getString("username"); // 封装User user.setId(id); user.setUsername(username); } System.out.println(user); } catch ( Exception e) { e.printStackTrace(); } finally { // 释放资源 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(); } } }
Melihat kod tersebut, kami boleh menemui masalah berikut dengan menggunakan JDBC untuk mengendalikan pangkalan data:
Penciptaan dan pelepasan sambungan pangkalan data yang kerap menyebabkan pembaziran sumber sistem, sekali gus menjejaskan prestasi sistem.
Kami menulis pernyataan SQL dalam kod Kod ini tidak mudah untuk dikekalkan dalam aplikasi sebenar.
Menggunakan preparedStatement untuk menghantar parameter kepada simbol yang diduduki adalah dikod keras, kerana keadaan di mana pernyataan sql tidak pasti, mungkin lebih atau kurang untuk mengubah suai sql, anda perlu mengubah suai kod, dan sistem ini tidak mudah diselenggara.
Terdapat pengekodan keras (nama lajur pertanyaan) dalam penghuraian set hasil perubahan membawa kepada perubahan dalam kod penghuraian, dan sistem tidak mudah diselenggara boleh dikapsulkan ke dalam objek pojo, ia akan menjadi lebih mudah untuk menghuraikan
Idea penyelesaian masalah
Gunakan kumpulan sambungan pangkalan data untuk memulakan sambungan. sumber untuk mengelakkan pembaziran sumber
Ekstrak pernyataan sql ke dalam konfigurasi xml ini hanya perlu memfokuskan pada fail xml. Ia tidak lebih baik daripada menulis semula sql dalam a sekumpulan kod java Teknologi seperti introspeksi secara automatik memetakan entiti ke medan jadual.
Tulis sendiri rangka kerja lapisan kegigihan
Kita boleh menggunakan c3p0 terus untuk kumpulan sambungan pangkalan data ComboPooledDataSource yang disediakan boleh digunakan
Untuk menyelesaikan masalah pengekodan keras sql, kita perlu menulis sql ke dalam fail xml, jadi secara semula jadi kita perlu mentakrifkan fail xml.
Sql sahaja sudah pasti tidak mencukupi Lagipun, kita perlu menyambung ke pangkalan data terlebih dahulu sebelum pernyataan sql mempunyai apa-apa makna. Oleh itu, maklumat konfigurasi data mesti ditakrifkan dahulu dalam xml, dan kemudian pernyataan sql.
1. Tentukan fail xml konfigurasi
Kami mencipta sqlMapConfig.xml baharu, mentakrifkan maklumat sumber data dan menambah dua pernyataan sql, parameterType ialah parameter pelaksanaan sql dan resultType ialah entiti pulangan kaedah.
<configuration> <!--数据库连接信息--> <property name="driverClass" value="com.mysql.cj.jdbc.Driver"/> <!-- <property name="driverClass" value="com.mysql.jdbc.Driver"/>--> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/mybatis?serverTimezone=Asia/Shanghai"/> <property name="username" value="root"/> <property name="password" value="mimashi3124"/> <select id="selectOne" parameterType="org.example.pojo.User" resultType="org.example.pojo.User"> select * from user where id = #{id} and username =#{username} </select> <select id="selectList" resultType="org.example.pojo.User"> select * from user </select> </configuration>
Konfigurasi xml yang dioptimumkan kini seperti ini
sqlMapConfig.xml
mapper.xml<configuration> <!--数据库连接信息--> <property name="driverClass" value="com.mysql.cj.jdbc.Driver"/> <!-- <property name="driverClass" value="com.mysql.jdbc.Driver"/>--> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/mybatis?serverTimezone=Asia/Shanghai"/> <property name="username" value="root"/> <property name="password" value="mimashi3124"/> <!--引⼊sql配置信息--> <mapper resource="mapper.xml"></mapper> </configuration>
<mapper namespace="user"> <select id="selectOne" parameterType="org.example.pojo.User" resultType="org.example.pojo.User"> select * from user where id = #{id} and username =#{username} </select> <select id="selectList" resultType="org.example.pojo.User"> select * from user </select> </mapper>
public class User { private int id; private String username; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } @Override public String toString() { return "User{" + "id=" + id + ", username='" + username + '\'' + '}'; } }
Mula-mula memperkenalkan kebergantungan maven
Kod adalah seperti berikut (versi pemacu mysql dilaraskan mengikut versi mysql sebenar yang digunakan):
Konfigurasi entiti konfigurasi pangkalan dataEntiti maklumat penyata SQL
<properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.22</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>
Dengan cara ini, tentukan kelas Sumber untuk membaca aliran fail xml
public class Configuration { //数据源 private DataSource dataSource; //map集合: key:statementId value:MappedStatement private Map<String,MappedStatement> mappedStatementMap = new HashMap<>(); public DataSource getDataSource() { return dataSource; } public Configuration setDataSource(DataSource dataSource) { this.dataSource = dataSource; return this; } public Map<String, MappedStatement> getMappedStatementMap() { return mappedStatementMap; } public Configuration setMappedStatementMap(Map<String, MappedStatement> mappedStatementMap) { this.mappedStatementMap = mappedStatementMap; return this; } }
langkah seterusnya ialah penghuraian sebenar, kerana Terdapat banyak kod penghuraian, jadi kami mempertimbangkan untuk merangkum kelas untuk mengendalikan penghuraian secara berasingan
public class MappedStatement { //id private String id; //sql语句 private String sql; //输⼊参数 private String parameterType; //输出参数 private String resultType; public String getId() { return id; } public MappedStatement setId(String id) { this.id = id; return this; } public String getSql() { return sql; } public MappedStatement setSql(String sql) { this.sql = sql; return this; } public String getParameterType() { return parameterType; } public MappedStatement setParameterType(String parameterType) { this.parameterType = parameterType; return this; } public String getResultType() { return resultType; } public MappedStatement setResultType(String resultType) { this.resultType = resultType; return this; } }
public class Resources { public static InputStream getResourceAsSteam(String path) { return Resources.class.getClassLoader().getResourceAsStream(path); } }
Kini kita boleh mendapatkan objek Konfigurasi dengan memanggil kaedah penghuraian konfigurasi. Tetapi dalam penggunaan sebenar, kami pasti berharap saya dapat memberi anda maklumat konfigurasi dan pernyataan SQL, dan kemudian memanggil kaedah anda untuk mengembalikan hasilnya.
Jadi kita juga perlu mentakrifkan antara muka operasi pangkalan data (kelas)public class XMLConfigBuilder { private Configuration configuration; public XMLConfigBuilder() { this.configuration = new Configuration(); } public Configuration parserConfiguration(InputStream inputStream) throws DocumentException, PropertyVetoException, ClassNotFoundException { Document document = new SAXReader().read(inputStream); Element rootElement = document.getRootElement(); List<Element> propertyElements = rootElement.selectNodes("//property"); Properties properties = new Properties(); for (Element propertyElement : propertyElements) { String name = propertyElement.attributeValue("name"); String value = propertyElement.attributeValue("value"); properties.setProperty(name,value); } //解析到数据库配置信息,设置数据源信息 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); //将configuration传入XMLMapperBuilder中做sql语句解析。 XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(this.configuration); List<Element> mapperElements = rootElement.selectNodes("//mapper"); for (Element mapperElement : mapperElements) { String mapperPath = mapperElement.attributeValue("resource"); InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(mapperPath); xmlMapperBuilder.parse(resourceAsStream); } return configuration; } }
3 Tentukan antara muka operasi sql SqlSession
public class XMLMapperBuilder { private Configuration configuration; public XMLMapperBuilder(Configuration configuration) { this.configuration = configuration; } public void parse(InputStream inputStream) throws DocumentException, ClassNotFoundException { Document document = new SAXReader().read(inputStream); Element rootElement = document.getRootElement(); String namespace = rootElement.attributeValue("namespace"); List<Element> select = rootElement.selectNodes("select"); for (Element element : select) { //id的值 String id = element.attributeValue("id"); String parameterType = element.attributeValue("parameterType"); //输⼊参数 String resultType = element.attributeValue("resultType"); //返回参数 //statementId,后续调用通过statementId,找到对应的sql执行 String key = namespace + "." + id; //sql语句 String textTrim = element.getTextTrim(); //封装 mappedStatement MappedStatement mappedStatement = new MappedStatement(); mappedStatement.setId(id); mappedStatement.setParameterType(parameterType); mappedStatement.setResultType(resultType); mappedStatement.setSql(textTrim); //填充 configuration configuration.getMappedStatementMap().put(key, mappedStatement); } } }
Pelaksanaan konkrit antara muka operasi SqlSession, di sini terutamanya melalui. statementId Cari maklumat sql yang sepadan dan laksanakannya
public interface SqlSession { //查询多个 public <E> List<E> selectList(String statementId, Object... param) throws Exception; //查询一个 public <T> T selectOne(String statementId,Object... params) throws Exception; }
Kaedah selectList dalam kelas operasi pangkalan data DefaultSqlSession memanggil kaedah simpleExcutor.query()
Prosesnya adalahpublic class DefaultSqlSession implements SqlSession { private Configuration configuration; private Executor simpleExcutor = new SimpleExecutor(); public DefaultSqlSession(Configuration configuration) { this.configuration = configuration; } @Override public <E> List<E> selectList(String statementId, Object... param) throws Exception { MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId); List<E> query = simpleExcutor.query(configuration, mappedStatement, param); return query; } @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("返回结果过多"); } } }
diperolehi mengikut statementId yang sepadan dengan pernyataan sql yang akan dilaksanakan, parameter panggilan dan parameter pulangan.
public class SimpleExecutor implements Executor { private Connection connection = null; @Override public <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object[] params) throws Exception { //获取连接 connection = configuration.getDataSource().getConnection(); // select * from user where id = #{id} and username = #{username} String sql = String sql = mappedStatement.getSql(); //对sql进⾏处理 BoundSql boundSql = getBoundSql(sql); // 3.获取预处理对象:preparedStatement PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSqlText()); // 4. 设置参数 //获取到了参数的全路径 String parameterType = mappedStatement.getParameterType(); Class<?> parameterTypeClass = getClassType(parameterType); List<ParameterMapping> parameterMappingList = boundSql.getParameterMappingList(); for (int i = 0; i < parameterMappingList.size(); i++) { ParameterMapping parameterMapping = parameterMappingList.get(i); String content = parameterMapping.getContent(); //反射 Field declaredField = parameterTypeClass.getDeclaredField(content); //暴力访问 declaredField.setAccessible(true); Object o = declaredField.get(params[0]); preparedStatement.setObject(i+1,o); } // 5. 执行sql ResultSet resultSet = preparedStatement.executeQuery(); String resultType = mappedStatement.getResultType(); Class<?> resultTypeClass = getClassType(resultType); ArrayList<Object> objects = new ArrayList<>(); // 6. 封装返回结果集 while (resultSet.next()){ Object o =resultTypeClass.newInstance(); //元数据 ResultSetMetaData metaData = resultSet.getMetaData(); for (int i = 1; i <= metaData.getColumnCount(); i++) { // 字段名 String columnName = metaData.getColumnName(i); // 字段的值 Object value = resultSet.getObject(columnName); //使用反射或者内省,根据数据库表和实体的对应关系,完成封装 PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, resultTypeClass); Method writeMethod = propertyDescriptor.getWriteMethod(); writeMethod.invoke(o,value); } objects.add(o); } return (List<E>) objects; } @Override public void close() throws SQLException { } private Class<?> getClassType(String parameterType) throws ClassNotFoundException { if(parameterType!=null){ Class<?> aClass = Class.forName(parameterType); return aClass; } return null; } private BoundSql getBoundSql(String sql) { //标记处理类:主要是配合通⽤标记解析器GenericTokenParser类完成对配置⽂件等的解 析⼯作,其中 //TokenHandler主要完成处理 ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler(); //GenericTokenParser :通⽤的标记解析器,完成了代码⽚段中的占位符的解析,然后再根 据给定的 // 标记处理器(TokenHandler)来进⾏表达式的处理 //三个参数:分别为openToken (开始标记)、closeToken (结束标记)、handler (标记处 理器) GenericTokenParser genericTokenParser = new GenericTokenParser("#{", "}", parameterMappingTokenHandler); String parse = genericTokenParser.parse(sql); List<ParameterMapping> parameterMappings = parameterMappingTokenHandler.getParameterMappings(); return new BoundSql(parse, parameterMappings); } }
我们还需要一个东西把两者给串起来,这里我们可以使用工厂模式来生成SqlSession
使用工厂模式创建SqlSession
public interface SqlSessionFactory { public SqlSession openSession(); }
public class DefaultSqlSessionFactory implements SqlSessionFactory{ private Configuration configuration; public DefaultSqlSessionFactory(Configuration configuration) { this.configuration = configuration; } @Override public SqlSession openSession() { return new DefaultSqlSession(configuration); } }
同时为了屏蔽构建SqlSessionFactory工厂类时获取Configuration的解析过程,我们可以使用构建者模式来获得一个SqlSessionFactory类。
public class SqlSessionFactoryBuilder { public SqlSessionFactory build(InputStream inputStream) throws PropertyVetoException, DocumentException, ClassNotFoundException { XMLConfigBuilder xmlConfigerBuilder = new XMLConfigBuilder(); Configuration configuration = xmlConfigerBuilder.parserConfiguration(inputStream); SqlSessionFactory sqlSessionFactory = new DefaultSqlSessionFactory(configuration); return sqlSessionFactory; } }
终于好了,通过以上几个步骤我们现在可以具体调用执行代码了。
public static void main(String[] args) throws Exception { InputStream resourceAsSteam = Resources.getResourceAsSteam("sqlMapConfig.xml"); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsSteam); SqlSession sqlSession = sqlSessionFactory.openSession(); User user = new User(); user.setId(1); user.setUsername("盖伦"); User user2 = sqlSession.selectOne("user.selectOne", user); System.out.println(user2); List<User> users = sqlSession.selectList("user.selectList"); for (User user1 : users) { System.out.println(user1); } }
代码正确执行,输出
上述⾃定义框架,解决了JDBC操作数据库带来的⼀些问题:例如频繁创建释放数据库连接,硬编
码,⼿动封装返回结果集等问题,现在我们继续来分析刚刚完成的⾃定义框架代码,有没有什么问题呢?
问题如下:
dao的实现类中存在重复的代码,整个操作的过程模板重复(创建sqlsession,调⽤sqlsession⽅ 法,关闭sqlsession)
dao的实现类中存在硬编码,调⽤sqlsession的⽅法时,参数statement的id硬编码
我们可以使用代理模式,生成代理对象,在调用之前获取到执行方法的方法名、具体类。这样我们就能获取到statementId。
为SqlSession类新增getMappper方法,获取代理对象
public interface SqlSession { public <E> List<E> selectList(String statementId, Object... param) throws Exception; public <T> T selectOne(String statementId,Object... params) throws Exception; //为Dao接口生成代理实现类 public <T> T getMapper(Class<?> mapperClass); }
public class DefaultSqlSession implements SqlSession { private Configuration configuration; private Executor simpleExcutor = new SimpleExecutor(); public DefaultSqlSession(Configuration configuration) { this.configuration = configuration; } @Override public <E> List<E> selectList(String statementId, Object... param) throws Exception { MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId); List<E> query = simpleExcutor.query(configuration, mappedStatement, param); return query; } @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("返回结果过多"); } } @Override public <T> T getMapper(Class<?> mapperClass) { Object proxyInstance = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(), new Class[]{mapperClass}, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // selectOne String methodName = method.getName(); // className:namespace String className = method.getDeclaringClass().getName(); //statementId String statementId = className+'.'+methodName; Type genericReturnType = method.getGenericReturnType(); //判断是否实现泛型类型参数化 if (genericReturnType instanceof ParameterizedType){ List<Object> objects = selectList(statementId,args); return objects; } return selectOne(statementId,args); } }); return (T) proxyInstance; } }
定义业务数据dao接口
public interface IUserDao { //查询所有用户 public List<User> findAll() throws Exception; //根据条件进行用户查询 public User findByCondition(User user) throws Exception; }
接下来我们只需获取到代理对象,调用方法即可。
public class Main2 { public static void main(String[] args) throws Exception { InputStream resourceAsSteam = Resources.getResourceAsSteam("sqlMapConfig.xml"); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsSteam); SqlSession sqlSession = sqlSessionFactory.openSession(); //获取到代理对象 IUserDao userDao = sqlSession.getMapper(IUserDao.class); List<User> all = userDao.findAll(); for (User user1 : all) { System.out.println(user1); } } }
Atas ialah kandungan terperinci Bagaimana untuk menulis rangka kerja lapisan kegigihan dalam Java. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!