Heim >Java >javaLernprogramm >So schreiben Sie das Persistenzschicht-Framework in Java handschriftlich

So schreiben Sie das Persistenzschicht-Framework in Java handschriftlich

PHPz
PHPznach vorne
2023-04-18 19:37:381327Durchsuche

JDBC-Betriebsüberprüfung und Problemanalyse

Studenten, die Java lernen, müssen mit JDBC vertraut gewesen sein, mit dem wir während der Anfängerphase in Kontakt gekommen sind

# 🎜🎜#Der folgende Code stellt eine Verbindung zur Datenbank her, um Benutzertabelleninformationen abzufragen. Die Benutzertabellenfelder sind Benutzer-ID und Benutzername.

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();
                }
            }
        }

Wenn wir uns den Code ansehen, können wir die folgenden Probleme bei der Verwendung von JDBC zum Betrieb der Datenbank feststellen:

  • Häufiges Erstellen und Freigeben von Datenbankverbindungen Dies führt zu einer Verschwendung von Systemressourcen und beeinträchtigt die Systemleistung.

  • Wir schreiben SQL-Anweisungen in den Code. In tatsächlichen Anwendungen kann sich SQL stark ändern, und Änderungen in SQL erfordern Änderungen in Java Code.

  • Die Verwendung von PreparedStatement zum Übergeben von Parametern an besetzte Symbole erfordert eine harte Codierung, da die Where-Bedingung der SQL-Anweisung ungewiss ist, sie kann mehr oder weniger sein, und das ist der Fall Um den SQL-Code zu ändern, muss er geändert werden. Das System ist nicht einfach zu warten.

  • Es gibt eine harte Codierung (Name der Abfragespalte) beim Parsen der Ergebnismenge. SQL-Änderungen führen zu Änderungen im Parsing-Code und das System ist nicht einfach zu warten. Wenn die Datenbankeinträge in die Pojo-Objektanalyse eingekapselt werden können, ist dies einfacher Datenbankverbindungspool, um Verbindungsressourcen zu initialisieren und Ressourcen zu vermeiden. Das ist nicht besser, als SQL in einer Menge Java-Code umzuschreiben zu Tabellenfeldern.

Schreiben Sie selbst ein Persistenzschicht-Framework

Als nächstes lösen wir die oben genannten Probleme nacheinander
  • Für die Im Datenbankverbindungspool können wir die von c3p0 bereitgestellte ComboPooledDataSource direkt verwenden Datei.

    SQL allein reicht definitiv nicht aus. Schließlich müssen wir erst eine Verbindung zur Datenbank herstellen, bevor die SQL-Anweisung irgendeine Bedeutung hat. Daher müssen die Datenkonfigurationsinformationen zuerst in XML und dann in der SQL-Anweisung definiert werden.
  • 1. Definieren Sie die Konfigurations-XML-Datei

    Wir erstellen eine neue sqlMapConfig.xml, definieren die Datenquelleninformationen und fügen zwei SQL-Anweisungen hinzu. ParameterType ist der SQL-Ausführungsparameter. und resultType ist die Methode, die die Entität zurückgibt.
  • Der Code lautet wie folgt (verschiedene Versionen der Datenbank verwenden möglicherweise unterschiedliche Treiberklassen):

    <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>

    Da nun auch die XML-Dateidatenbankinformationen und die SQL-Anweisung verfügbar sind Definition ist auch verfügbar, welche anderen Probleme gibt es?
Unsere tatsächlichen SQL-Operationen umfassen verschiedene Tabellen. Daher verbessern wir sie und fügen die SQL-Anweisungen jeder Tabelle in eine separate XML-Datei ein, sodass die Struktur klarer und einfacher zu verwalten ist.

Die optimierte XML-Konfiguration sieht jetzt so aus

sqlMapConfig.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.xml

<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>

By the So definieren Sie die Geschäftseinheit User

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=&#39;" + username + &#39;\&#39;&#39; +
                &#39;}&#39;;
    }
}

2. Lesen Sie die Konfigurationsdatei

Nachdem der Lesevorgang abgeschlossen ist, liegt sie in Form eines Streams vor, was nicht einfach ist Wir müssen also eine Analyse durchführen, um die Informationen zu erhalten, und Entitätsobjekte zum Speichern erstellen.

Konfiguration: speichert grundlegende Datenbankinformationen, Mapper> eindeutige Kennung: Namespace + „.“ + idMappedStatement: speichert SQL-Anweisungen, Eingabeparametertypen, Ausgabeparametertypen ##🎜 🎜#XML-Analyse verwenden wir dom4j

Führen Sie zuerst die Maven-Abhängigkeit ein

Der Code lautet wie folgt (die MySQL-Treiberversion wird entsprechend der tatsächlich verwendeten MySQL-Version angepasst): # ?? 🎜#
<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>

Connect Als nächstes folgt das eigentliche Parsen. Da es viele Parsing-Codes gibt, erwägen wir, die Klasse zu kapseln, um das Parsing separat durchzuführen.

Definieren Sie die XMLConfigBuilder-Klasse, um die Datenbankkonfiguration zu parsen Information

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;
    }
}

Definieren Sie die XMLMapperBuilder-Klasse, um die Datenbankkonfiguration zu analysieren. Information

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;
    }
}

Jetzt können wir das Konfigurationsobjekt abrufen, indem wir die Konfigurationsanalysemethode aufrufen. Bei der tatsächlichen Verwendung hoffen wir jedoch auf jeden Fall, dass ich Ihnen Konfigurationsinformationen und SQL-Anweisungen geben und dann Ihre Methode aufrufen kann, um das Ergebnis zurückzugeben.

Also müssen wir auch eine Datenbankoperationsschnittstelle (Klasse) definieren

3. Definieren Sie die SQL-Operationsschnittstelle SqlSession

public class Resources {
    public static InputStream getResourceAsSteam(String path) {
        return Resources.class.getClassLoader().getResourceAsStream(path);
    }
}

Konkrete Implementierung der Operationsschnittstelle SqlSession, Hier ist das Wichtigste. Die entsprechenden SQL-Informationen werden über die Anweisungs-ID gefunden und ausgeführt. Datenbankausführungslogik schreiben# 🎜🎜#

Die selectList-Methode in der Datenbankoperationsklasse DefaultSqlSession ruft die Methode simpleExcutor.query() auf

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;
    }
}

Die obigen Kommentare sind detaillierter und der Prozess ist# 🎜🎜#

#🎜 🎜#

Rufen Sie die auszuführende SQL-Anweisung ab, rufen Sie Parameter auf und geben Sie Parameter gemäß der entsprechenden Anweisungs-ID zurück.

Parsen Sie den SQL-Platzhalter und legen Sie die Aufrufparameter fest Ermitteln Sie den entsprechenden Wert durch Reflektion und legen Sie die SQL-Anweisungsparameter fest , schließen Sie die Einstellung der Objekteigenschaften ab und geben Sie schließlich das Ergebnis zurück.

Durch die obigen Schritte haben wir die Datenbankkonfiguration und die SQL-Anweisungsinformationen erhalten. Die Datenbankbetriebsklasse SqlSession ist definiert, aber wir rufen die Parsing-Konfigurationsdatei nirgendwo auf.

我们还需要一个东西把两者给串起来,这里我们可以使用工厂模式来生成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;
    }
}

5.调用测试

终于好了,通过以上几个步骤我们现在可以具体调用执行代码了。

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

代码正确执行,输出

So schreiben Sie das Persistenzschicht-Framework in Java handschriftlich

⾃定义框架优化

上述⾃定义框架,解决了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+&#39;.&#39;+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);
        }
    }
}

So schreiben Sie das Persistenzschicht-Framework in Java handschriftlich

Das obige ist der detaillierte Inhalt vonSo schreiben Sie das Persistenzschicht-Framework in Java handschriftlich. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:yisu.com. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen