Автор: Брат Сяофу
Блог:bugstack.cn
Осаждайте, делитесь, растите и позвольте себе и другим получить что-то! 😄
Введение
Mybatis
Основной принцип является и наиболее удобным его воплощением Почему вы так говорите?
Потому что, когда мы используем Mybatis, нам нужно только определить интерфейс, которому не нужно писать класс реализации, и мы можем выполнять операции CRUD в базе данных, аннотируя или настраивая операторы SQL.
Итак, как это делается?Одним из очень важных моментов является то, что в Spring вы можете передать свой прокси-объект контейнеру Spring.Этот прокси-объект можно рассматривать как конкретный класс реализации интерфейса DAO, а прокси-реализация класс может выполнить операцию над базой данных, то есть этот процесс инкапсуляции называется инфраструктурой ORM.
Поговорив об основном процессе, давайте проведем несколько тестов, чтобы каждый мог с ним работать!Чтобы получить знания, вы должны начать, прежде чем вы сможете их получить! Вы можете попрактиковаться со следующими репозиториями исходного кода
GitHub.com/заместитель комиссара/…
Во-вторых, подключите Bean-компонент к контейнеру Spring, выполнив несколько шагов.
- Что касается технических сценариев регистрации Bean, MyBatis является наиболее распространенным среди технических фреймворков, которые мы используем каждый день. Только определяя интерфейс при использовании MyBatis, нет необходимости писать класс реализации, но этот интерфейс можно связать с настроенным оператором SQL, и соответствующий результат может быть возвращен при выполнении соответствующей операции с базой данных. Тогда работа этого интерфейса и базы данных использует прокси и регистрацию Бина.
- Все мы знаем, что вызов класса не может напрямую вызывать нереализованный интерфейс, поэтому необходимо сгенерировать соответствующий класс реализации для интерфейса через прокси. Затем поместите прокси-класс в реализацию FactoryBean Spring и, наконец, зарегистрируйте класс реализации FactoryBean в контейнере Spring. Теперь, когда ваш прокси-класс зарегистрирован в контейнере Spring, его можно внедрить в свойства с помощью аннотации.
В соответствии с этой реализацией, давайте поработаем и посмотрим, как в коде реализован процесс регистрации бина.
1. Определите интерфейс
public interface IUserDao {
String queryUserInfo();
}
- Сначала определите интерфейс, похожий на DAO.В принципе, такой интерфейс очень распространен при использовании MyBatis. Мы проксируем и регистрируем этот интерфейс позже.
2. Реализация прокси класса
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
Class<?>[] classes = {IUserDao.class};
InvocationHandler handler = (proxy, method, args) -> "你被代理了 " + method.getName();
IUserDao userDao = (IUserDao) Proxy.newProxyInstance(classLoader, classes, handler);
String res = userDao.queryUserInfo();
logger.info("测试结果:{}", res);
- Сам прокси-метод Java относительно прост в использовании, и его использование также очень фиксировано.
- InvocationHandler — это интерфейсный класс, а его соответствующее содержимое реализации — это конкретная реализация прокси-объекта.
- Последнее, что нужно сделать, это передать прокси в Proxy для создания прокси-объекта,
Proxy.newProxyInstance
.
3. Внедрить фабрику бобов
public class ProxyBeanFactory implements FactoryBean {
@Override
public Object getObject() throws Exception {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
Class[] classes = {IUserDao.class};
InvocationHandler handler = (proxy, method, args) -> "你被代理了 " + method.getName();
return Proxy.newProxyInstance(classLoader, classes, handler);
}
@Override
public Class<?> getObjectType() {
return IUserDao.class;
}
}
- FactoryBean играет роль второго мастера Spring, у него почти 70 младших братьев (реализующих определение интерфейса), поэтому у него три метода;
- T getObject() выдает исключение; возвращает объект экземпляра компонента
- Class> getObjectType(); возвращает тип экземпляра класса
- boolean isSingleton(); Определите, является ли это синглтоном, синглтон будет помещен в пул кеша с одним экземпляром в контейнере Spring
- Здесь мы помещаем объект, используя Java-прокси выше, в метод getObject(), затем объект, полученный из Spring, является нашим прокси-объектом.
4. Регистрация бина
public class RegisterBeanFactory implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(ProxyBeanFactory.class);
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(beanDefinition, "userDao");
BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, registry);
}
}
В управлении bean-компонентами Spring все bean-компоненты в конечном итоге будут зарегистрированы в классе DefaultListableBeanFactory.Основное содержимое приведенного выше кода включает:
- Реализуйте метод BeanDefinitionRegistryPostProcessor.postProcessBeanDefinitionRegistry, чтобы получить объект регистрации компонента.
- Define Bean, GenericBeanDefinition, здесь мы в основном настраиваем нашу фабрику прокси-классов.
- Создайте класс обработки определения bean-компонента, BeanDefinitionHolder, основные параметры, необходимые здесь; определите bean-компонент и имя
setBeanClass(ProxyBeanFactory.class)
. - Наконец, зарегистрируйте наш собственный компонент в контейнере spring, register.registerBeanDefinition().
5. Тестовая проверка
Выше мы зарегистрировали пользовательский прокси-бин в контейнере Spring. Теперь давайте проверим, как вызывается прокси-бин.
1. Определить spring-config.xml
<bean id="userDao" class="org.itstack.interview.bean.RegisterBeanFactory"/>
- Здесь мы настраиваем RegisterBeanFactory в XML-конфигурацию Spring для легкой загрузки при запуске.
2. Модульное тестирование
@Test
public void test_IUserDao() {
BeanFactory beanFactory = new ClassPathXmlApplicationContext("spring-config.xml");
IUserDao userDao = beanFactory.getBean("userDao", IUserDao.class);
String res = userDao.queryUserInfo();
logger.info("测试结果:{}", res);
}
Результаты теста
22:53:14.759 [main] DEBUG o.s.c.e.PropertySourcesPropertyResolver - Could not find key 'spring.liveBeansView.mbeanDomain' in any property source
22:53:14.760 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Returning cached instance of singleton bean 'userDao'
22:53:14.796 [main] INFO org.itstack.interview.test.ApiTest - 测试结果:你被代理了 queryUserInfo
Process finished with exit code 0
- Как видно из результатов теста, мы уже можем достичь ожидаемых результатов, внедрив объект прокси-бина в Spring.
- Фактически, этот процесс также используется во многих фреймворках, особенно при разработке некоторого промежуточного программного обеспечения, необходимо использовать аналогичные фреймворки ORM.
3. Напишите Mybatis вручную
Расширьте предыдущий проект анализа исходного кода itsstack-demo-mybatis, добавьте аналогичный пакет и имитируйте проект Mybatis. Скачать полный протоколGitHub.com/заместитель комиссара/…
itstack-demo-mybatis
└── src
├── main
│ ├── java
│ │ └── org.itstack.demo
│ │ ├── dao
│ │ │ ├── ISchool.java
│ │ │ └── IUserDao.java
│ │ ├── like
│ │ │ ├── Configuration.java
│ │ │ ├── DefaultSqlSession.java
│ │ │ ├── DefaultSqlSessionFactory.java
│ │ │ ├── Resources.java
│ │ │ ├── SqlSession.java
│ │ │ ├── SqlSessionFactory.java
│ │ │ ├── SqlSessionFactoryBuilder.java
│ │ │ └── SqlSessionFactoryBuilder.java
│ │ └── interfaces
│ │ ├── School.java
│ │ └── User.java
│ ├── resources
│ │ ├── mapper
│ │ │ ├── School_Mapper.xml
│ │ │ └── User_Mapper.xml
│ │ ├── props
│ │ │ └── jdbc.properties
│ │ ├── spring
│ │ │ ├── mybatis-config-datasource.xml
│ │ │ └── spring-config-datasource.xml
│ │ ├── logback.xml
│ │ ├── mybatis-config.xml
│ │ └── spring-config.xml
│ └── webapp
│ └── WEB-INF
└── test
└── java
└── org.itstack.demo.test
├── ApiLikeTest.java
├── MybatisApiTest.java
└── SpringApiTest.java
Что касается всей демо-версии, то она предназначена не для того, чтобы реализовать все Mybatis, а для того, чтобы показать вам самый основной контент, вы будете чувствовать себя точно так же от использования, но классы реализации были заменены, а основные классы включают в себя;
- Configuration
- DefaultSqlSession
- DefaultSqlSessionFactory
- Resources
- SqlSession
- SqlSessionFactory
- SqlSessionFactoryBuilder
- XNode
1. Сначала протестируйте весь фреймворк DemoJdbc
ApiLikeTest.test_queryUserInfoById()
@Test
public void test_queryUserInfoById() {
String resource = "spring/mybatis-config-datasource.xml";
Reader reader;
try {
reader = Resources.getResourceAsReader(resource);
SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader);
SqlSession session = sqlMapper.openSession();
try {
User user = session.selectOne("org.itstack.demo.dao.IUserDao.queryUserInfoById", 1L);
System.out.println(JSON.toJSONString(user));
} finally {
session.close();
reader.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
Все идет хорошо и результат таков (новички часто сталкиваются с различными проблемами);
{"age":18,"createTime":1576944000000,"id":1,"name":"水水","updateTime":1576944000000}
Process finished with exit code 0
На первый взгляд, этот тестовый класс может быть точно таким же, как код, тестируемый MybatisApiTest.java, и разницы нет. На самом деле их импортные пакеты отличаются;
Пакеты, представленные в MybatisApiTest.java
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
Пакеты, представленные в ApiLikeTest.java
import org.itstack.demo.like.Resources;
import org.itstack.demo.like.SqlSession;
import org.itstack.demo.like.SqlSessionFactory;
import org.itstack.demo.like.SqlSessionFactoryBuilder;
хорошо! Далее приступаем к анализу этой части основного кода.
2. Загрузите файл конфигурации XML
Здесь мы используем для разбора структуру конфигурационного файла mybatis, которая максимально приближена к исходному коду, не разрушая исходную структуру. Когда mybatis используется отдельно, используются два файла конфигурации: конфигурация источника данных, конфигурация сопоставления Mapper, как показано ниже;
mybatis-config-datasource.xml и конфигурация источника данных
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/itstack?useUnicode=true"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mapper/User_Mapper.xml"/>
<mapper resource="mapper/School_Mapper.xml"/>
</mappers>
</configuration>
Конфигурация сопоставления User_Mapper.xml и Mapper
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.itstack.demo.dao.IUserDao">
<select id="queryUserInfoById" parameterType="java.lang.Long" resultType="org.itstack.demo.po.User">
SELECT id, name, age, createTime, updateTime
FROM user
where id = #{id}
</select>
<select id="queryUserList" parameterType="org.itstack.demo.po.User" resultType="org.itstack.demo.po.User">
SELECT id, name, age, createTime, updateTime
FROM user
where age = #{age}
</select>
</mapper>
Процесс загрузки здесь отличается от mybaits, мы используем способ dom4j. В случае первого приобретения ресурсов вы увидите следующее;
ApiLikeTest.test_queryUserInfoById() и частичный перехват
String resource = "spring/mybatis-config-datasource.xml";
Reader reader;
try {
reader = Resources.getResourceAsReader(resource);
...
Из вышеизложенного видно, что это процесс получения потока чтения через адрес конфигурационного файла, чтобы заложить основу для последующего анализа. Во-первых, давайте посмотрим на класс Resources, который является нашим классом ресурсов.
Resources.java и классы ресурсов
/**
* 博 客 | https://bugstack.cn
* Create by 小傅哥 @2020
*/
public class Resources {
public static Reader getResourceAsReader(String resource) throws IOException {
return new InputStreamReader(getResourceAsStream(resource));
}
private static InputStream getResourceAsStream(String resource) throws IOException {
ClassLoader[] classLoaders = getClassLoaders();
for (ClassLoader classLoader : classLoaders) {
InputStream inputStream = classLoader.getResourceAsStream(resource);
if (null != inputStream) {
return inputStream;
}
}
throw new IOException("Could not find resource " + resource);
}
private static ClassLoader[] getClassLoaders() {
return new ClassLoader[]{
ClassLoader.getSystemClassLoader(),
Thread.currentThread().getContextClassLoader()};
}
}
Запись этого метода кода — getResourceAsReader, пока это не будет сделано ниже;
- Получите коллекцию ClassLoaders, чтобы максимизировать файлы конфигурации поиска
- Прочитайте ресурс конфигурации через classLoader.getResourceAsStream и вернитесь сразу после его обнаружения, в противном случае выдайте исключение.
3. Разберите файл конфигурации XML
После загрузки конфигурационного файла начинается операция разбора Здесь мы также имитируем mybatis, но упрощаем его следующим образом;
SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader);
SqlSessionFactoryBuilder.build() и класс сборки входа
public DefaultSqlSessionFactory build(Reader reader) {
SAXReader saxReader = new SAXReader();
try {
Document document = saxReader.read(new InputSource(reader));
Configuration configuration = parseConfiguration(document.getRootElement());
return new DefaultSqlSessionFactory(configuration);
} catch (DocumentException e) {
e.printStackTrace();
}
return null;
}
- Создайте класс документа с XML-анализом, прочитав поток
- parseConfiguration для синтаксического анализа XML-файла и установки результата в класс конфигурации, включая пул соединений, источник данных, отношения сопоставления.
SqlSessionFactoryBuilder.parseConfiguration() и процесс синтаксического анализа
private Configuration parseConfiguration(Element root) {
Configuration configuration = new Configuration();
configuration.setDataSource(dataSource(root.selectNodes("//dataSource")));
configuration.setConnection(connection(configuration.dataSource));
configuration.setMapperElement(mapperElement(root.selectNodes("mappers")));
return configuration;
}
- Как вы можете видеть в предыдущем содержимом xml, нам нужно проанализировать источник данных с информацией о пуле соединений с базой данных, а также сопоставители отношений сопоставления операторов базы данных.
SqlSessionFactoryBuilder.dataSource() и анализировать источник данных
private Map<String, String> dataSource(List<Element> list) {
Map<String, String> dataSource = new HashMap<>(4);
Element element = list.get(0);
List content = element.content();
for (Object o : content) {
Element e = (Element) o;
String name = e.attributeValue("name");
String value = e.attributeValue("value");
dataSource.put(name, value);
}
return dataSource;
}
- Этот процесс относительно прост, вам нужно только получить информацию об источнике данных.
SqlSessionFactoryBuilder.connection() и получить соединение с базой данных
private Connection connection(Map<String, String> dataSource) {
try {
Class.forName(dataSource.get("driver"));
return DriverManager.getConnection(dataSource.get("url"), dataSource.get("username"), dataSource.get("password"));
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
}
return null;
}
- Это самый примитивный код jdbc, который получает пул соединений с базой данных.
SqlSessionFactoryBuilder.mapperElement() и оператор SQL Parse
private Map<String, XNode> mapperElement(List<Element> list) {
Map<String, XNode> map = new HashMap<>();
Element element = list.get(0);
List content = element.content();
for (Object o : content) {
Element e = (Element) o;
String resource = e.attributeValue("resource");
try {
Reader reader = Resources.getResourceAsReader(resource);
SAXReader saxReader = new SAXReader();
Document document = saxReader.read(new InputSource(reader));
Element root = document.getRootElement();
//命名空间
String namespace = root.attributeValue("namespace");
// SELECT
List<Element> selectNodes = root.selectNodes("select");
for (Element node : selectNodes) {
String id = node.attributeValue("id");
String parameterType = node.attributeValue("parameterType");
String resultType = node.attributeValue("resultType");
String sql = node.getText();
// ? 匹配
Map<Integer, String> parameter = new HashMap<>();
Pattern pattern = Pattern.compile("(#\\{(.*?)})");
Matcher matcher = pattern.matcher(sql);
for (int i = 1; matcher.find(); i++) {
String g1 = matcher.group(1);
String g2 = matcher.group(2);
parameter.put(i, g2);
sql = sql.replace(g1, "?");
}
XNode xNode = new XNode();
xNode.setNamespace(namespace);
xNode.setId(id);
xNode.setParameterType(parameterType);
xNode.setResultType(resultType);
xNode.setSql(sql);
xNode.setParameter(parameter);
map.put(namespace + "." + id, xNode);
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
return map;
}
- Этот процесс сначала включает в себя синтаксический анализ всех операторов sql, в настоящее время только синтаксический анализ выбора, связанный с тестированием.
- Для того, чтобы подтвердить уникальность всех операторов sql, они используются, id в namespace + select склеивается как ключ, а затем сохраняется в карте вместе с sql.
- В конфигурации оператора sql mybaits есть заполнители для передачи параметров. где id = #{id} Итак, нам нужно установить плейсхолдер в виде вопросительного знака, а также нужно сохранить информацию о заказе и имя плейсхолдера в структуре карты, что удобно для последующей установки входных параметров запроса.
4. Создайте DefaultSqlSessionFactory
Наконец, инициализированный класс конфигурации Configuration создается как параметр DefaultSqlSessionFactory, как показано ниже.
public DefaultSqlSessionFactory build(Reader reader) {
SAXReader saxReader = new SAXReader();
try {
Document document = saxReader.read(new InputSource(reader));
Configuration configuration = parseConfiguration(document.getRootElement());
return new DefaultSqlSessionFactory(configuration);
} catch (DocumentException e) {
e.printStackTrace();
}
return null;
}
Класс реализации DefaultSqlSessionFactory.java и SqlSessionFactory
public class DefaultSqlSessionFactory implements SqlSessionFactory {
private final Configuration configuration;
public DefaultSqlSessionFactory(Configuration configuration) {
this.configuration = configuration;
}
@Override
public SqlSession openSession() {
return new DefaultSqlSession(configuration.connection, configuration.mapperElement);
}
}
- Этот процесс относительно прост, конструктор предоставляет только входные параметры класса конфигурации.
- Реализуйте openSession() из SqlSessionFactory для создания DefaultSqlSession, который также может выполнять операции sql.
5. Откройте SqlSession
SqlSession session = sqlMapper.openSession();
Вышеупомянутый шаг заключается в создании DefaultSqlSession, что относительно просто. следующее;
@Override
public SqlSession openSession() {
return new DefaultSqlSession(configuration.connection, configuration.mapperElement);
}
6. Выполните оператор SQL
User user = session.selectOne("org.itstack.demo.dao.IUserDao.queryUserInfoById", 1L);
Реализуя SqlSession в DefaultSqlSession, предоставьте запрос оператора базы данных и закройте пул соединений, как показано ниже.
SqlSession.java и определение
public interface SqlSession {
<T> T selectOne(String statement);
<T> T selectOne(String statement, Object parameter);
<T> List<T> selectList(String statement);
<T> List<T> selectList(String statement, Object parameter);
void close();
}
Затем посмотрите на конкретный процесс выполнения, session.selectOne
DefaultSqlSession.selectOne() и выполнить запрос
public <T> T selectOne(String statement, Object parameter) {
XNode xNode = mapperElement.get(statement);
Map<Integer, String> parameterMap = xNode.getParameter();
try {
PreparedStatement preparedStatement = connection.prepareStatement(xNode.getSql());
buildParameter(preparedStatement, parameter, parameterMap);
ResultSet resultSet = preparedStatement.executeQuery();
List<T> objects = resultSet2Obj(resultSet, Class.forName(xNode.getResultType()));
return objects.get(0);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
-
selectOne - это objects.get(0); все возвращается selectList
-
Получите сохраненную информацию о теге выбора при первоначальном анализе xml через оператор;
<select id="queryUserInfoById" parameterType="java.lang.Long" resultType="org.itstack.demo.po.User"> SELECT id, name, age, createTime, updateTime FROM user where id = #{id} </select>
-
После получения оператора sql передайте его классу PreparedStatement jdbc для выполнения.
-
Здесь нам также нужно установить входные параметры, мы извлечем настройки входных параметров следующим образом;
private void buildParameter(PreparedStatement preparedStatement, Object parameter, Map<Integer, String> parameterMap) throws SQLException, IllegalAccessException { int size = parameterMap.size(); // 单个参数 if (parameter instanceof Long) { for (int i = 1; i <= size; i++) { preparedStatement.setLong(i, Long.parseLong(parameter.toString())); } return; } if (parameter instanceof Integer) { for (int i = 1; i <= size; i++) { preparedStatement.setInt(i, Integer.parseInt(parameter.toString())); } return; } if (parameter instanceof String) { for (int i = 1; i <= size; i++) { preparedStatement.setString(i, parameter.toString()); } return; } Map<String, Object> fieldMap = new HashMap<>(); // 对象参数 Field[] declaredFields = parameter.getClass().getDeclaredFields(); for (Field field : declaredFields) { String name = field.getName(); field.setAccessible(true); Object obj = field.get(parameter); field.setAccessible(false); fieldMap.put(name, obj); } for (int i = 1; i <= size; i++) { String parameterDefine = parameterMap.get(i); Object obj = fieldMap.get(parameterDefine); if (obj instanceof Short) { preparedStatement.setShort(i, Short.parseShort(obj.toString())); continue; } if (obj instanceof Integer) { preparedStatement.setInt(i, Integer.parseInt(obj.toString())); continue; } if (obj instanceof Long) { preparedStatement.setLong(i, Long.parseLong(obj.toString())); continue; } if (obj instanceof String) { preparedStatement.setString(i, obj.toString()); continue; } if (obj instanceof Date) { preparedStatement.setDate(i, (java.sql.Date) obj); } } }
- Один параметр относительно просто установить значение напрямую, Long, Integer, String...
- Если это объект класса, он должен соответствовать параметру Map, получив свойство Field.
-
Выполнить запрос после установки параметров подготовленногоStatement.executeQuery()
-
Затем нам нужно преобразовать результат запроса в наш класс (в основном операции класса отражения), resultSet2Obj(resultSet, Class.forName(xNode.getResultType()));
private <T> List<T> resultSet2Obj(ResultSet resultSet, Class<?> clazz) { List<T> list = new ArrayList<>(); try { ResultSetMetaData metaData = resultSet.getMetaData(); int columnCount = metaData.getColumnCount(); // 每次遍历行值 while (resultSet.next()) { T obj = (T) clazz.newInstance(); for (int i = 1; i <= columnCount; i++) { Object value = resultSet.getObject(i); String columnName = metaData.getColumnName(i); String setMethod = "set" + columnName.substring(0, 1).toUpperCase() + columnName.substring(1); Method method; if (value instanceof Timestamp) { method = clazz.getMethod(setMethod, Date.class); } else { method = clazz.getMethod(setMethod, value.getClass()); } method.invoke(obj, value); } list.add(obj); } } catch (Exception e) { e.printStackTrace(); } return list; }
- В основном сгенерируйте наш объект класса посредством отражения, тип этого класса определяется в теге sql.
- Тип времени необходимо оценивать после обработки, Timestamp, который не совпадает с типом java.
7. Дополнительные инструкции по SQL-запросу
SQL-запрос имеет входные параметры, некоторые не требуют ввода параметров, есть один запрос и набор запросов.Его нужно только разумно упаковать.Например, в следующем наборе запросов входным параметром является тип объекта;
ApiLikeTest.test_queryUserList()
@Test
public void test_queryUserList() {
String resource = "spring/mybatis-config-datasource.xml";
Reader reader;
try {
reader = Resources.getResourceAsReader(resource);
SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader);
SqlSession session = sqlMapper.openSession();
try {
User req = new User();
req.setAge(18);
List<User> userList = session.selectList("org.itstack.demo.dao.IUserDao.queryUserList", req);
System.out.println(JSON.toJSONString(userList));
} finally {
session.close();
reader.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
**Результаты теста:
[{"age":18,"createTime":1576944000000,"id":1,"name":"水水","updateTime":1576944000000},{"age":18,"createTime":1576944000000,"id":2,"name":"豆豆","updateTime":1576944000000}]
Process finished with exit code 0
Четыре, анализ исходного кода (mybatis)
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.6</version>
</dependency>
Весь исходный код Mybatis по-прежнему очень велик.Следующее в основном организует и анализирует часть основного контента, чтобы облегчить последующий анализ части исходного кода интеграции Mybatis и Spring. Кратко включает: инициализацию контейнера, анализ файла конфигурации, загрузку Mapper и динамический прокси.
1. Начните с простого случая
Чтобы изучить исходный код Mybatis, лучше всего начать с простой точки зрения, а не начинать с интеграции Spring. SqlSessionFactory — это основной объект экземпляра всего Mybatis, а экземпляр объекта SqlSessionFactory получается через объект SqlSessionFactoryBuilder. Объект SqlSessionFactoryBuilder может загружать сведения о конфигурации из файла конфигурации XML, а затем создавать SqlSessionFactory. Следующий пример:
MybatisApiTest.java
public class MybatisApiTest {
@Test
public void test_queryUserInfoById() {
String resource = "spring/mybatis-config-datasource.xml";
Reader reader;
try {
reader = Resources.getResourceAsReader(resource);
SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader);
SqlSession session = sqlMapper.openSession();
try {
User user = session.selectOne("org.itstack.demo.dao.IUserDao.queryUserInfoById", 1L);
System.out.println(JSON.toJSONString(user));
} finally {
session.close();
reader.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
dao/IUserDao.java
public interface IUserDao {
User queryUserInfoById(Long id);
}
spring/mybatis-config-datasource.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/itstack?useUnicode=true"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mapper/User_Mapper.xml"/>
</mappers>
</configuration>
Если все пойдет хорошо, результат будет следующим:
{"age":18,"createTime":1571376957000,"id":1,"name":"花花","updateTime":1571376957000}
Как видно из приведенного выше блока кода, основной код SqlSessionFactoryBuilder().build(reader) отвечает за загрузку, синтаксический анализ и создание файлов конфигурации Mybatis до тех пор, пока он, наконец, не будет выполнен и возвращен через SqlSession.
2. Инициализация контейнера
Как видно из приведенного выше кода, SqlSessionFactory создается с помощью класса фабрики SqlSessionFactoryBuilder, а не напрямую с помощью конструктора. Процесс загрузки файла конфигурации и инициализации контейнера выглядит следующим образом:
- Основной класс процесса
- SqlSessionFactoryBuilder
- XMLConfigBuilder
- XPathParser
- Configuration
SqlSessionFactoryBuilder.java
public class SqlSessionFactoryBuilder {
public SqlSessionFactory build(Reader reader) {
return build(reader, null, null);
}
public SqlSessionFactory build(Reader reader, String environment) {
return build(reader, environment, null);
}
public SqlSessionFactory build(Reader reader, Properties properties) {
return build(reader, null, properties);
}
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
reader.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
public SqlSessionFactory build(InputStream inputStream) {
return build(inputStream, null, null);
}
public SqlSessionFactory build(InputStream inputStream, String environment) {
return build(inputStream, environment, null);
}
public SqlSessionFactory build(InputStream inputStream, Properties properties) {
return build(inputStream, null, properties);
}
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
}
Как видно из приведенного выше исходного кода, SqlSessionFactory предоставляет три способа создания объектов;
- Поток байтов: java.io.InputStream
- Поток символов: java.io.Reader
- Класс конфигурации: org.apache.ibatis.session.Configuration
Затем поток байтов и поток символов создадут класс синтаксического анализа файла конфигурации: XMLConfigBuilder, сгенерируют конфигурацию с помощью parser.parse() и, наконец, вызовут метод построения класса конфигурации для создания SqlSessionFactory.
XMLConfigBuilder.java
public class XMLConfigBuilder extends BaseBuilder {
private boolean parsed;
private final XPathParser parser;
private String environment;
private final ReflectorFactory localReflectorFactory = new DefaultReflectorFactory();
...
public XMLConfigBuilder(Reader reader, String environment, Properties props) {
this(new XPathParser(reader, true, props, new XMLMapperEntityResolver()), environment, props);
}
...
}
- XMLConfigBuilder делегирует загрузку и синтаксический анализ XML-файлов XPathParser и, наконец, использует javax.xml, поставляемый с JDK, для синтаксического анализа XML (XPath).
- XPathParser(Reader reader, boolean validation, Properties variables, EntityResolver entityResolver)
- читатель: создает новый источник ввода, используя поток символов для чтения файлов XML
- проверка: следует ли выполнять проверку DTD
- переменные: информация о конфигурации свойств
- entityResolver: Mybatis жестко запрограммировал новый XMLMapperEntityResolver() для обеспечения парсера XML по умолчанию.
XMLMapperEntityResolver.java
public class XMLMapperEntityResolver implements EntityResolver {
private static final String IBATIS_CONFIG_SYSTEM = "ibatis-3-config.dtd";
private static final String IBATIS_MAPPER_SYSTEM = "ibatis-3-mapper.dtd";
private static final String MYBATIS_CONFIG_SYSTEM = "mybatis-3-config.dtd";
private static final String MYBATIS_MAPPER_SYSTEM = "mybatis-3-mapper.dtd";
private static final String MYBATIS_CONFIG_DTD = "org/apache/ibatis/builder/xml/mybatis-3-config.dtd";
private static final String MYBATIS_MAPPER_DTD = "org/apache/ibatis/builder/xml/mybatis-3-mapper.dtd";
/*
* Converts a public DTD into a local one
*
* @param publicId The public id that is what comes after "PUBLIC"
* @param systemId The system id that is what comes after the public id.
* @return The InputSource for the DTD
*
* @throws org.xml.sax.SAXException If anything goes wrong
*/
@Override
public InputSource resolveEntity(String publicId, String systemId) throws SAXException {
try {
if (systemId != null) {
String lowerCaseSystemId = systemId.toLowerCase(Locale.ENGLISH);
if (lowerCaseSystemId.contains(MYBATIS_CONFIG_SYSTEM) || lowerCaseSystemId.contains(IBATIS_CONFIG_SYSTEM)) {
return getInputSource(MYBATIS_CONFIG_DTD, publicId, systemId);
} else if (lowerCaseSystemId.contains(MYBATIS_MAPPER_SYSTEM) || lowerCaseSystemId.contains(IBATIS_MAPPER_SYSTEM)) {
return getInputSource(MYBATIS_MAPPER_DTD, publicId, systemId);
}
}
return null;
} catch (Exception e) {
throw new SAXException(e.toString());
}
}
private InputSource getInputSource(String path, String publicId, String systemId) {
InputSource source = null;
if (path != null) {
try {
InputStream in = Resources.getResourceAsStream(path);
source = new InputSource(in);
source.setPublicId(publicId);
source.setSystemId(systemId);
} catch (IOException e) {
// ignore, null is ok
}
}
return source;
}
}
- Mybatis полагается на файлы dtd для анализа, среди которых ibatis-3-config.dtd в основном используется для целей совместимости.
- Вызов getInputSource(String path, String publicId, String systemId) имеет два параметра publicId (публичный идентификатор) и systemId (системный идентификатор)
XPathParser.java
public XPathParser(Reader reader, boolean validation, Properties variables, EntityResolver entityResolver) {
commonConstructor(validation, variables, entityResolver);
this.document = createDocument(new InputSource(reader));
}
private void commonConstructor(boolean validation, Properties variables, EntityResolver entityResolver) {
this.validation = validation;
this.entityResolver = entityResolver;
this.variables = variables;
XPathFactory factory = XPathFactory.newInstance();
this.xpath = factory.newXPath();
}
private Document createDocument(InputSource inputSource) {
// important: this must only be called AFTER common constructor
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setValidating(validation);
factory.setNamespaceAware(false);
factory.setIgnoringComments(true);
factory.setIgnoringElementContentWhitespace(false);
factory.setCoalescing(false);
factory.setExpandEntityReferences(true);
DocumentBuilder builder = factory.newDocumentBuilder();
builder.setEntityResolver(entityResolver);
builder.setErrorHandler(new ErrorHandler() {
@Override
public void error(SAXParseException exception) throws SAXException {
throw exception;
}
@Override
public void fatalError(SAXParseException exception) throws SAXException {
throw exception;
}
@Override
public void warning(SAXParseException exception) throws SAXException {
}
});
return builder.parse(inputSource);
} catch (Exception e) {
throw new BuilderException("Error creating document instance. Cause: " + e, e);
}
}
-
Сверху вниз видно, что основная цель — создать анализатор документов Mybatis и, наконец, вернуть документ в соответствии с builder.parse(inputSource)
-
После получения экземпляра XPathParser следующим методом является вызов:this(new XPathParser(reader, true, props, new XMLMapperEntityResolver()), environment, props);
XMLConfigBuilder.this(new XPathParser(reader, true, props, new XMLMapperEntityResolver()), environment, props); private XMLConfigBuilder(XPathParser parser, String environment, Properties props) { super(new Configuration()); ErrorContext.instance().resource("SQL Mapper Configuration"); this.configuration.setVariables(props); this.parsed = false; this.environment = environment; this.parser = parser; }
-
который вызывает конструктор родительского класса
public abstract class BaseBuilder { protected final Configuration configuration; protected final TypeAliasRegistry typeAliasRegistry; protected final TypeHandlerRegistry typeHandlerRegistry; public BaseBuilder(Configuration configuration) { this.configuration = configuration; this.typeAliasRegistry = this.configuration.getTypeAliasRegistry(); this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry(); } }
-
После создания XMLConfigBuilder sqlSessionFactoryBuild вызывает функцию parser.parse() для создания конфигурации.
public class XMLConfigBuilder extends BaseBuilder { public Configuration parse() { if (parsed) { throw new BuilderException("Each XMLConfigBuilder can only be used once."); } parsed = true; parseConfiguration(parser.evalNode("/configuration")); return configuration; } }
3. Разбор файла конфигурации
Эта часть является основным содержанием всего разбора и загрузки XML-файла, в том числе;
- парсинг свойств propertiesElement
- Загрузить настройки узла settingsAsProperties
- Загрузить пользовательскую VFS loadCustomVfs
- псевдоним типа разрешения typeAliasesElement
- Загрузите плагин pluginElement
- Загрузите фабрику объектов objectFactoryElement
- Создать фабрику-оболочку объекта objectWrapperFactoryElement
- Загрузите фабрику отражений ReflectorFactoryElement
- настройки элемента
- Загрузить конфигурацию среды
- Идентификатор поставщика базы данных загружается с помощью databaseIdProviderElement.
- Загрузить обработчик типа typeHandlerElement
- (основной) загрузите файл сопоставления mapperElement
parseConfiguration(parser.evalNode("/configuration"));
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
//属性解析propertiesElement
propertiesElement(root.evalNode("properties"));
//加载settings节点settingsAsProperties
Properties settings = settingsAsProperties(root.evalNode("settings"));
//加载自定义VFS loadCustomVfs
loadCustomVfs(settings);
//解析类型别名typeAliasesElement
typeAliasesElement(root.evalNode("typeAliases"));
//加载插件pluginElement
pluginElement(root.evalNode("plugins"));
//加载对象工厂objectFactoryElement
objectFactoryElement(root.evalNode("objectFactory"));
//创建对象包装器工厂objectWrapperFactoryElement
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
//加载反射工厂reflectorFactoryElement
reflectorFactoryElement(root.evalNode("reflectorFactory"));
//元素设置
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
//加载环境配置environmentsElement
environmentsElement(root.evalNode("environments"));
//数据库厂商标识加载databaseIdProviderElement
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
//加载类型处理器typeHandlerElement
typeHandlerElement(root.evalNode("typeHandlers"));
//加载mapper文件mapperElement
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
Нижний уровень всех root.evalNode() заключается в вызове метода XML DOM: оценка объекта (строковое выражение, элемент объекта, тип возврата QName), выражение параметра выражения, возвращает окончательное содержимое узла через XObject resultObject = eval (выражение, элемент ), вы можете обратиться кNo Elevation.org/DTD/No Elevation…
<!ELEMENT configuration (properties?, settings?, typeAliases?, typeHandlers?, objectFactory?, objectWrapperFactory?, reflectorFactory?, plugins?, environments?, databaseIdProvider?, mappers?)>
<!ELEMENT databaseIdProvider (property*)>
<!ATTLIST databaseIdProvider
type CDATA #REQUIRED
>
<!ELEMENT properties (property*)>
<!ATTLIST properties
resource CDATA #IMPLIED
url CDATA #IMPLIED
>
<!ELEMENT property EMPTY>
<!ATTLIST property
name CDATA #REQUIRED
value CDATA #REQUIRED
>
<!ELEMENT settings (setting+)>
<!ELEMENT setting EMPTY>
<!ATTLIST setting
name CDATA #REQUIRED
value CDATA #REQUIRED
>
<!ELEMENT typeAliases (typeAlias*,package*)>
<!ELEMENT typeAlias EMPTY>
<!ATTLIST typeAlias
type CDATA #REQUIRED
alias CDATA #IMPLIED
>
<!ELEMENT typeHandlers (typeHandler*,package*)>
<!ELEMENT typeHandler EMPTY>
<!ATTLIST typeHandler
javaType CDATA #IMPLIED
jdbcType CDATA #IMPLIED
handler CDATA #REQUIRED
>
<!ELEMENT objectFactory (property*)>
<!ATTLIST objectFactory
type CDATA #REQUIRED
>
<!ELEMENT objectWrapperFactory EMPTY>
<!ATTLIST objectWrapperFactory
type CDATA #REQUIRED
>
<!ELEMENT reflectorFactory EMPTY>
<!ATTLIST reflectorFactory
type CDATA #REQUIRED
>
<!ELEMENT plugins (plugin+)>
<!ELEMENT plugin (property*)>
<!ATTLIST plugin
interceptor CDATA #REQUIRED
>
<!ELEMENT environments (environment+)>
<!ATTLIST environments
default CDATA #REQUIRED
>
<!ELEMENT environment (transactionManager,dataSource)>
<!ATTLIST environment
id CDATA #REQUIRED
>
<!ELEMENT transactionManager (property*)>
<!ATTLIST transactionManager
type CDATA #REQUIRED
>
<!ELEMENT dataSource (property*)>
<!ATTLIST dataSource
type CDATA #REQUIRED
>
<!ELEMENT mappers (mapper*,package*)>
<!ELEMENT mapper EMPTY>
<!ATTLIST mapper
resource CDATA #IMPLIED
url CDATA #IMPLIED
class CDATA #IMPLIED
>
<!ELEMENT package EMPTY>
<!ATTLIST package
name CDATA #REQUIRED
>
В файле определения mybatis-3-config.dtd есть 11 файлов конфигурации, а именно:
- properties?,
- settings?,
- typeAliases?,
- typeHandlers?,
- objectFactory?,
- objectWrapperFactory?,
- reflectorFactory?,
- plugins?,
- environments?,
- databaseIdProvider?,
- mappers?
Каждая из вышеперечисленных конфигураций не является обязательной. Окончательное содержимое конфигурации будет сохранено в org.apache.ibatis.session.Configuration следующим образом;
public class Configuration {
protected Environment environment;
// 允许在嵌套语句中使用分页(RowBounds)。如果允许使用则设置为false。默认为false
protected boolean safeRowBoundsEnabled;
// 允许在嵌套语句中使用分页(ResultHandler)。如果允许使用则设置为false。
protected boolean safeResultHandlerEnabled = true;
// 是否开启自动驼峰命名规则(camel case)映射,即从经典数据库列名 A_COLUMN 到经典 Java 属性名 aColumn 的类似映射。默认false
protected boolean mapUnderscoreToCamelCase;
// 当开启时,任何方法的调用都会加载该对象的所有属性。否则,每个属性会按需加载。默认值false (true in ≤3.4.1)
protected boolean aggressiveLazyLoading;
// 是否允许单一语句返回多结果集(需要兼容驱动)。
protected boolean multipleResultSetsEnabled = true;
// 允许 JDBC 支持自动生成主键,需要驱动兼容。这就是insert时获取mysql自增主键/oracle sequence的开关。注:一般来说,这是希望的结果,应该默认值为true比较合适。
protected boolean useGeneratedKeys;
// 使用列标签代替列名,一般来说,这是希望的结果
protected boolean useColumnLabel = true;
// 是否启用缓存 {默认是开启的,可能这也是你的面试题}
protected boolean cacheEnabled = true;
// 指定当结果集中值为 null 的时候是否调用映射对象的 setter(map 对象时为 put)方法,这对于有 Map.keySet() 依赖或 null 值初始化的时候是有用的。
protected boolean callSettersOnNulls;
// 允许使用方法签名中的名称作为语句参数名称。 为了使用该特性,你的工程必须采用Java 8编译,并且加上-parameters选项。(从3.4.1开始)
protected boolean useActualParamName = true;
//当返回行的所有列都是空时,MyBatis默认返回null。 当开启这个设置时,MyBatis会返回一个空实例。 请注意,它也适用于嵌套的结果集 (i.e. collectioin and association)。(从3.4.2开始) 注:这里应该拆分为两个参数比较合适, 一个用于结果集,一个用于单记录。通常来说,我们会希望结果集不是null,单记录仍然是null
protected boolean returnInstanceForEmptyRow;
// 指定 MyBatis 增加到日志名称的前缀。
protected String logPrefix;
// 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。一般建议指定为slf4j或log4j
protected Class <? extends Log> logImpl;
// 指定VFS的实现, VFS是mybatis提供的用于访问AS内资源的一个简便接口
protected Class <? extends VFS> vfsImpl;
// MyBatis 利用本地缓存机制(Local Cache)防止循环引用(circular references)和加速重复嵌套查询。 默认值为 SESSION,这种情况下会缓存一个会话中执行的所有查询。 若设置值为 STATEMENT,本地会话仅用在语句执行上,对相同 SqlSession 的不同调用将不会共享数据。
protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION;
// 当没有为参数提供特定的 JDBC 类型时,为空值指定 JDBC 类型。 某些驱动需要指定列的 JDBC 类型,多数情况直接用一般类型即可,比如 NULL、VARCHAR 或 OTHER。
protected JdbcType jdbcTypeForNull = JdbcType.OTHER;
// 指定对象的哪个方法触发一次延迟加载。
protected Set<String> lazyLoadTriggerMethods = new HashSet<String>(Arrays.asList(new String[] { "equals", "clone", "hashCode", "toString" }));
// 设置超时时间,它决定驱动等待数据库响应的秒数。默认不超时
protected Integer defaultStatementTimeout;
// 为驱动的结果集设置默认获取数量。
protected Integer defaultFetchSize;
// SIMPLE 就是普通的执行器;REUSE 执行器会重用预处理语句(prepared statements); BATCH 执行器将重用语句并执行批量更新。
protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
// 指定 MyBatis 应如何自动映射列到字段或属性。 NONE 表示取消自动映射;PARTIAL 只会自动映射没有定义嵌套结果集映射的结果集。 FULL 会自动映射任意复杂的结果集(无论是否嵌套)。
protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL;
// 指定发现自动映射目标未知列(或者未知属性类型)的行为。这个值应该设置为WARNING比较合适
protected AutoMappingUnknownColumnBehavior autoMappingUnknownColumnBehavior = AutoMappingUnknownColumnBehavior.NONE;
// settings下的properties属性
protected Properties variables = new Properties();
// 默认的反射器工厂,用于操作属性、构造器方便
protected ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
// 对象工厂, 所有的类resultMap类都需要依赖于对象工厂来实例化
protected ObjectFactory objectFactory = new DefaultObjectFactory();
// 对象包装器工厂,主要用来在创建非原生对象,比如增加了某些监控或者特殊属性的代理类
protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();
// 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。特定关联关系中可通过设置fetchType属性来覆盖该项的开关状态。
protected boolean lazyLoadingEnabled = false;
// 指定 Mybatis 创建具有延迟加载能力的对象所用到的代理工具。MyBatis 3.3+使用JAVASSIST
protected ProxyFactory proxyFactory = new JavassistProxyFactory(); // #224 Using internal Javassist instead of OGNL
// MyBatis 可以根据不同的数据库厂商执行不同的语句,这种多厂商的支持是基于映射语句中的 databaseId 属性。
protected String databaseId;
...
}
Как вы можете видеть выше, Mybatis поддерживает все конфигурации: resultMap, операторы Sql, плагины, кеши и т. д. в Configuration. Здесь также есть небольшая хитрость.Также в Configuration есть внутренний класс StrictMap, который наследуется от HashMap и улучшает обработку исключений антидупликации во время ввода и отсутствия значения, полученного во время получения, следующим образом;
protected static class StrictMap<V> extends HashMap<String, V> {
private static final long serialVersionUID = -4950446264854982944L;
private final String name;
public StrictMap(String name, int initialCapacity, float loadFactor) {
super(initialCapacity, loadFactor);
this.name = name;
}
public StrictMap(String name, int initialCapacity) {
super(initialCapacity);
this.name = name;
}
public StrictMap(String name) {
super();
this.name = name;
}
public StrictMap(String name, Map<String, ? extends V> m) {
super(m);
this.name = name;
}
}
(Core) Загрузить файл сопоставления mapperElement
Обработка файлов Mapper является основной службой платформы Mybatis. Все операторы SQL написаны в Mapper, который также находится в центре нашего анализа. Другие модули можно объяснить позже.
XMLConfigBuilder.parseConfiguration()->mapperElement(root.evalNode("mappers"));
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
// 如果要同时使用package自动扫描和通过mapper明确指定要加载的mapper,一定要确保package自动扫描的范围不包含明确指定的mapper,否则在通过package扫描的interface的时候,尝试加载对应xml文件的loadXmlResource()的逻辑中出现判重出错,报org.apache.ibatis.binding.BindingException异常,即使xml文件中包含的内容和mapper接口中包含的语句不重复也会出错,包括加载mapper接口时自动加载的xml mapper也一样会出错。
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
InputStream inputStream = Resources.getUrlAsStream(url);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url == null && mapperClass != null) {
Class<?> mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);
} else {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
-
Mybatis предоставляет два способа настройки Mapper.Первый — использовать режим автоматического поиска пакетов, чтобы все интерфейсы в указанном пакете были зарегистрированы как mapper, что также является более распространенным способом в Spring, например:
<mappers> <package name="org.itstack.demo"/> </mappers>
-
Другая категория — это явное указание Mapper, который может быть разделен, например, по ресурсу, URL-адресу или классу;
<mappers> <mapper resource="mapper/User_Mapper.xml"/> <mapper class=""/> <mapper url=""/> </mappers>
4. Загрузка маппера и динамический прокси
Автоматический поиск и загрузка с помощью метода пакета, а также создание соответствующего прокси-класса преобразователя, блока кода и процесса, как показано ниже.
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
...
}
}
}
}
Mapper загружается в процессе создания прокси-объектов.Основные основные классы включают в себя;
- XMLConfigBuilder
- Configuration
- MapperRegistry
- MapperAnnotationBuilder
- MapperProxyFactory
MapperRegistry.java
Разбор загрузки картографа
public void addMappers(String packageName, Class<?> superType) {
// mybatis框架提供的搜索classpath下指定package以及子package中符合条件(注解或者继承于某个类/接口)的类,默认使用Thread.currentThread().getContextClassLoader()返回的加载器,和spring的工具类殊途同归。
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
// 无条件的加载所有的类,因为调用方传递了Object.class作为父类,这也给以后的指定mapper接口预留了余地
resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
// 所有匹配的calss都被存储在ResolverUtil.matches字段中
Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
for (Class<?> mapperClass : mapperSet) {
//调用addMapper方法进行具体的mapper类/接口解析
addMapper(mapperClass);
}
}
Создать прокси-класс: MapperProxyFactory
public <T> void addMapper(Class<T> type) {
// 对于mybatis mapper接口文件,必须是interface,不能是class
if (type.isInterface()) {
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
// 为mapper接口创建一个MapperProxyFactory代理
knownMappers.put(type, new MapperProxyFactory<T>(type));
// It's important that the type is added before the parser is run
// otherwise the binding may automatically be attempted by the
// mapper parser. If the type is already known, it won't try.
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
Отношение сопоставления между классами интерфейса и прокси-проектами поддерживается в MapperRegistry, knownMappers;
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();
MapperProxyFactory.java
public class MapperProxyFactory<T> {
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();
public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
public Class<T> getMapperInterface() {
return mapperInterface;
}
public Map<Method, MapperMethod> getMethodCache() {
return methodCache;
}
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
}
Выше приведен проект класса прокси Mapper. mapperInterface в конструкторе является соответствующим классом интерфейса. При создании экземпляра будет получен конкретный прокси MapperProxy, который в основном содержит SqlSession.
V. Резюме
- Процесс анализа долгий и объемный, и вы, возможно, не сможете понять весь процесс за один день, но вы все равно можете получить много пользы, если у вас хватит терпения, чтобы немного изучить его. В дальнейшем, когда вы столкнетесь с такими аномалиями, вы легко сможете их решить, а также помочь собеседовать и набрать!
- Причина анализа Mybatis заключалась в том, чтобы сначала добавить пользовательские аннотации к Dao, но выяснилось, что этот аспект не может быть перехвачен. Думая, что это класс, который динамически проксируется, тогда слои часто опускаются до тех пор, пока MapperProxy.invoke! Конечно, Mybatis предоставляет разработку пользовательских плагинов.
- Приведенный выше анализ исходного кода является лишь анализом части основного содержания.Если вы хотите знать все, вы можете обратиться к информации, углубленный анализ исходного кода MyBatis 3 и отладка кода. В IDEA по-прежнему очень удобно смотреть исходный код, в том числе просматривать диаграмму классов, последовательность вызовов и т.д.
- На самом деле, самое важное в mybatis и mybatis-spring — это собрать классы разбора и интерфейса файла конфигурации Mapper в прокси-классы для сопоставления, чтобы облегчить операции CRUD в базе данных. Проанализировав исходный код, вы можете получить больше опыта программирования (подпрограмм).
- Ссылки, связанные с Mybatis;