序文 動的プロキシの原理を説明する前に、まず、mybatis を統合した後の dao 層の 2 つの実装方法、つまり動的プロキシを使用しない方法と動的プロキシを使用する方法を見てみましょう。独自の dao 層インターフェイスの実装と mybatis 動的プロキシを比較することで、mybatis 動的プロキシが何を行うかをより直感的に示し、動的プロキシのプロセスを理解するのに役立ちます。説明の後、動的プロキシの原理を分析します。この説明は、構築された mybatis 環境に基づいており、基本的なユーザー クラスの記述とユーザー クラスの Dao インターフェイスの宣言が実装されています。以下は、Dao 層のインターフェイス コードです。 パブリックインターフェースUserDao{ /* すべてのユーザー情報を照会*/ リスト<ユーザー> findAll(); /** * ユーザーを保存 * @param user */ void save(ユーザー user); /** * ユーザーを更新 * @return */ void update(ユーザー user); /** * ユーザーを削除 */ void 削除(Integer ユーザー ID); /** * ユーザーを検索 * @param userId * @戻る */ ユーザー findOne(Integer userId); /** * 名前に基づくあいまいクエリ* @param name * @戻る */ リスト<User> findByName(String name); /** * 結合オブジェクトに基づくファジークエリ* @param vo * @戻る */ リスト<User> findByQueryVo(QueryVo vo); } 1. Mybatis daoレイヤーの2つの実装方法の比較 1. daoレイヤーは動的プロキシを使用しない dao レイヤーが動的プロキシを使用しない場合は、dao レイヤーのインターフェースを自分で実装する必要があります。簡単にするために、私は Dao インターフェースに findAll メソッドのみを実装しました。このメソッドは、Dao を自分で実装する方法を示す例として使用されています。コードを見てみましょう。 パブリッククラス UserDaoImpl は UserDao を実装します{ プライベート SqlSessionFactory ファクトリ。 パブリックUserDaoImpl(SqlSessionFactoryファクトリー){ this.factory = ファクトリー; } パブリックリスト<ユーザー> findAll() { //1. sqlSession オブジェクトを取得します。SqlSession sqlSession = factory.openSession(); //2. selectList メソッドを呼び出します。List<User> list = sqlSession.selectList("com.example.dao.UserDao.findAll"); //3. ストリームを閉じる sqlSession.close(); リストを返します。 } パブリック void save(ユーザー user) { } パブリック void update(ユーザー user) { } パブリック void delete(整数ユーザーID) { } パブリックユーザーfindOne(整数ユーザーID) { null を返します。 } パブリックリスト<ユーザー> findByName(文字列名) { null を返します。 } パブリックリスト<ユーザー> findByQueryVo(QueryVo vo) { null を返します。 } ここでのキー コードは List<User> list = sqlSession.selectList("com.example.dao.UserDao.findAll") であり、SqlSession でメソッドを手動で呼び出す必要があります。動的プロキシ メソッドに基づく最終目標は、ここで正常に呼び出すことです。 注: 操作を追加、更新、または削除する場合は、メソッドにトランザクションの送信を追加する必要があります。 2. daoレイヤーはMybatisの動的プロキシを使用する 動的プロキシを使用する場合、Dao 層のインターフェース宣言が完了したら、使用時に SqlSession オブジェクトの getMapper メソッドを通じて Dao インターフェースに対応するプロキシ オブジェクトを取得するだけです。キー コードは次のとおりです。 //3. SqlSession オブジェクトを取得します。SqlSession session = factory.openSession(); //4. dao のプロキシ オブジェクトを取得します。 UserDao mapper = session.getMapper(UserDao.class); //5. すべてのクエリメソッドを実行します。List<User> list = mapper.findAll(); dao レイヤーのプロキシ オブジェクトを取得した後、プロキシ オブジェクトを介してクエリ メソッドを呼び出して、すべてのユーザーのリストを照会する機能を実現できます。 2. Mybatis 動的プロキシ実装の原理分析 動的プロキシで最も重要なクラス: SqlSession、MapperProxy、MapperMethod。エントリ メソッドから呼び出しの終了までのプロセス分析を始めましょう。 1. 呼び出しメソッドの開始: //4. daoのプロキシオブジェクトを取得する UserDao mapper = session.getMapper(UserDao.class); SqlSesseion はインターフェースなので、デバッグ モードを通じて、ここで使用されている実装クラスは DefaultSqlSession であることがわかりました。 2. DeaultSqlSession の getMapper メソッドを見つけ、ここでは他のアクションが実行されていないことを確認します。作業は Configuration クラスにスローされるだけです。Configuration はクラスであり、インターフェイスではありません。このクラスの getMapper メソッドに直接入力できます。 @オーバーライド パブリック <T> T getMapper(クラス<T> 型) { 構成を返します。<T>getMapper(type, this); } 3. Configuration クラスの getMapper メソッドを見つけます。ここでは、作業は MapperRegistry の getMapper メソッドに引き継がれるので、先へ進みます。 パブリック <T> T getMapper(クラス<T> 型、SqlSession sqlSession) { mapperRegistry.getMapper(type, sqlSession) を返します。 } 4. MapperRegistry の getMapper メソッドを見つけます。前と異なることがわかります。MapperProxyFactory の命名方法から、対象の MapperProxy のプロキシ クラスがこのファクトリを通じて生成されることがわかります。次に、 mapperProxyFactory.newInstance(sqlSession); を通じて MapperProxyFactory の newInstance メソッドに入ります。 パブリック <T> T getMapper(クラス<T> 型、SqlSession sqlSession) { 最終的な MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type); マッパープロキシファクトリーが null の場合 throw new BindingException("タイプ " + type + " は MapperRegistry に認識されていません。"); } 試す { mapperProxyFactory.newInstance(sqlSession) を返します。 } キャッチ (例外 e) { throw new BindingException("マッパーインスタンスの取得中にエラーが発生しました。原因: " + e, e); } } 5. MapperProxyFactory の newIntance メソッドを見つけます。パラメータ タイプ SqlSession から、上記の呼び出しが最初に 2 番目の newInstance メソッドに入り、注目する必要がある MapperProxy オブジェクトを作成することがわかります。次に、2 番目のメソッドは最初の newInstance メソッドを呼び出し、MapperProxy オブジェクトをそれに渡します。プロキシ クラスはオブジェクトに基づいて作成され、返されます。必要なプロキシ クラスはすでにここにありますが、プロキシ クラスが何を行うかを確認するには、MapperProxy クラスまで調べる必要があります。 保護された T 新しいインスタンス (MapperProxy<T> mapperProxy) { (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), 新しい Class[] { mapperInterface }, mapperProxy); を返します。 } パブリック T newInstance(SqlSession sqlSession) { 最終的な MapperProxy<T> mapperProxy = 新しい MapperProxy<T>(sqlSession、mapperInterface、methodCache); 新しいインスタンス(mapperProxy)を返します。 } 6. MapperProxy クラスを見つけ、JDK 動的プロキシが実装する必要がある InvocationHandler インターフェイスを実装していることを確認します。そのため、invoke() メソッドに注目します。ここで、invoke メソッドで最初に MapperMethod クラスが取得され、次に mapperMethod.execute() が呼び出されることがわかります。そのため、MapperMethod クラスの execute メソッドを引き続き確認します。 パブリッククラス MapperProxy<T> は InvocationHandler と Serializable を実装します { プライベート静的最終ロングシリアルバージョンUID = -6424540398559729838L; プライベート最終 SqlSession sqlSession; プライベート最終 Class<T> マッパーインターフェース; プライベート最終 Map<Method, MapperMethod> methodCache; パブリック MapperProxy(SqlSession sqlSession、Class<T> mapperInterface、Map<Method、MapperMethod> methodCache) { this.sqlSession は sqlSession です。 マッパーインターフェース this.methodCache = メソッドキャッシュ; } @オーバーライド パブリックオブジェクトinvoke(オブジェクトプロキシ、メソッドメソッド、オブジェクト[]引数)throwsThrowable{ 試す { (Object.class.equals(method.getDeclaringClass())) の場合 { メソッドを呼び出します(this, args); } そうでなければ (isDefaultMethod(method)) { 戻り値:invokeDefaultMethod(プロキシ、メソッド、引数); } } キャッチ (Throwable t) { ExceptionUtil.unwrapThrowable(t) をスローします。 } 最終的な MapperMethod mapperMethod = cachedMapperMethod(method); mapperMethod.execute(sqlSession, args) を返します。 } プライベートMapperMethod cachedMapperMethod(メソッドメソッド) { MapperMethod mapperMethod = methodCache.get(method); マッパーメソッドが null の場合 マッパーメソッド = 新しい MapperMethod(マッパーインターフェース、メソッド、sqlSession.getConfiguration()); メソッドCache.put(メソッド、mapperMethod); } mapperMethod を返します。 } @Java7 を使用 プライベートオブジェクトinvokeDefaultMethod(オブジェクトプロキシ、メソッドメソッド、オブジェクト[]引数) スロー可能なものをスローします { 最終コンストラクタ<MethodHandles.Lookup> コンストラクタ = MethodHandles.Lookup.class .getDeclaredConstructor(Class.class、int.class); コンストラクタがアクセス可能である場合 コンストラクターでアクセス可能を設定します(true); } 最終的な Class<?> 宣言クラス = method.getDeclaringClass(); 戻りコンストラクタ .newInstance(宣言クラス、 メソッドハンドル.ルックアップ.プライベート | メソッドハンドル.ルックアップ.保護された | MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC) .unreflectSpecial(メソッド、宣言クラス).bindTo(プロキシ).invokeWithArguments(引数); } /** * java.lang.reflect.Method#isDefault() のバックポート */ プライベートブール値isDefaultMethod(メソッドメソッド) { 戻り値 ((method.getModifiers() & (Modifier.ABSTRACT | Modifier.PUBLIC | Modifier.STATIC)) == Modifier.PUBLIC) && メソッド.getDeclaringClass().isInterface(); } } 7. MapperMethod クラスの execute メソッドを見つけ、execute がこのクラスの他のメソッドを呼び出して戻り結果を取得してカプセル化していることを確認します。MapperMethod クラス全体を見てみましょう。 パブリックオブジェクト実行(SqlSession sqlSession、オブジェクト[] args) { オブジェクトの結果; スイッチ(コマンド.getType()){ ケース挿入: { オブジェクト param = method.convertArgsToSqlCommandParam(args); 結果 = rowCountResult(sqlSession.insert(command.getName(), param)); 壊す; } ケース更新: { オブジェクト param = method.convertArgsToSqlCommandParam(args); 結果 = rowCountResult(sqlSession.update(command.getName(), param)); 壊す; } 削除の場合: { オブジェクト param = method.convertArgsToSqlCommandParam(args); 結果 = rowCountResult(sqlSession.delete(command.getName(), param)); 壊す; } ケース選択: メソッドがVoidを返す場合、メソッドはResultHandlerを持ちます。 結果ハンドラを実行します(sqlSession、引数); 結果 = null; } それ以外の場合 (method.returnsMany()) { 結果 = executeForMany(sqlSession, 引数); } それ以外の場合 (method.returnsMap()) { 結果 = executeForMap(sqlSession, args); } それ以外の場合 (method.returnsCursor()) { 結果 = executeForCursor(sqlSession, 引数); } それ以外 { オブジェクト param = method.convertArgsToSqlCommandParam(args); 結果 = sqlSession.selectOne(command.getName(), パラメータ); } 壊す; FLUSHの場合: 結果 = sqlSession.flushStatements(); 壊す; デフォルト: throw new BindingException("次の不明な実行メソッド: " + command.getName()); } 結果が null の場合、method.getReturnType().isPrimitive() と method.returnsVoid() が返されます。 新しいBindingExceptionをスローします("Mapperメソッド '" + command.getName() + " は、プリミティブ戻り値の型 (" + method.getReturnType() + ") を持つメソッドから null を返そうとしました。"); } 結果を返します。 } 8. MapperMethod クラスは、プロキシ メカニズム全体のコア クラスであり、SqlSession の操作をカプセル化して使用します。 このクラスには、SqlCommand と MethodSignature という 2 つの内部クラスがあります。 SqlCommand は、xml で構成した操作のノードである CRUD 操作をカプセル化するために使用されます。各ノードは MappedStatement クラスを生成します。 MethodSignature は、メソッドのパラメータと戻り値の型をカプセル化するために使用されます。execute メソッドでは、SqlSession のインターフェイス呼び出しに戻っています。これは、SqlSession オブジェクトを直接使用して DefaultSqlSession の実装クラスを呼び出すことで UerDao インターフェイスを実装する方法と同じです。プロキシの大きな循環の後、元の場所に戻ります。これが、動的プロキシ全体の実装プロセスです。 パブリッククラス MapperMethod { プライベート最終 SqlCommand コマンド; プライベート最終 MethodSignature メソッド。 パブリック MapperMethod(Class<?> mapperInterface、メソッド method、構成 config) { this.command = 新しい SqlCommand(config、mapperInterface、メソッド); this.method = 新しい MethodSignature(config、mapperInterface、method); } パブリックオブジェクト実行(SqlSession sqlSession、オブジェクト[] args) { オブジェクトの結果; スイッチ(コマンド.getType()){ ケース挿入: { オブジェクト param = method.convertArgsToSqlCommandParam(args); 結果 = rowCountResult(sqlSession.insert(command.getName(), param)); 壊す; } ケース更新: { オブジェクト param = method.convertArgsToSqlCommandParam(args); 結果 = rowCountResult(sqlSession.update(command.getName(), param)); 壊す; } 削除の場合: { オブジェクト param = method.convertArgsToSqlCommandParam(args); 結果 = rowCountResult(sqlSession.delete(command.getName(), param)); 壊す; } ケース選択: メソッドがVoidを返す場合、メソッドはResultHandlerを持ちます。 結果ハンドラを実行します(sqlSession、引数); 結果 = null; } それ以外の場合 (method.returnsMany()) { 結果 = executeForMany(sqlSession, 引数); } それ以外の場合 (method.returnsMap()) { 結果 = executeForMap(sqlSession, args); } それ以外の場合 (method.returnsCursor()) { 結果 = executeForCursor(sqlSession, 引数); } それ以外 { オブジェクト param = method.convertArgsToSqlCommandParam(args); 結果 = sqlSession.selectOne(command.getName(), パラメータ); } 壊す; FLUSHの場合: 結果 = sqlSession.flushStatements(); 壊す; デフォルト: throw new BindingException("次の不明な実行メソッド: " + command.getName()); } 結果が null の場合、method.getReturnType().isPrimitive() と method.returnsVoid() が返されます。 新しいBindingExceptionをスローします("Mapperメソッド '" + command.getName() + " は、プリミティブ戻り値の型 (" + method.getReturnType() + ") を持つメソッドから null を返そうとしました。"); } 結果を返します。 } プライベートオブジェクト rowCountResult(int rowCount) { 最終オブジェクトの結果。 if (メソッド.returnsVoid()) { 結果 = null; } そうでない場合 (Integer.class.equals(method.getReturnType()) || Integer.TYPE.equals(method.getReturnType())) { 結果 = rowCount; } そうでない場合 (Long.class.equals(method.getReturnType()) || Long.TYPE.equals(method.getReturnType())) { 結果 = (long)rowCount; } そうでない場合 (Boolean.class.equals(method.getReturnType()) || Boolean.TYPE.equals(method.getReturnType())) { 結果 = rowCount > 0; } それ以外 { throw new BindingException("Mapper メソッド '" + command.getName() + "' にはサポートされていない戻り値の型 " + method.getReturnType() があります); } 結果を返します。 } プライベート void executeWithResultHandler(SqlSession sqlSession, Object[] args) { MappedStatement ms = sqlSession.getConfiguration().getMappedStatement(command.getName()); void.class.equals(ms.getResultMaps().get(0).getType()) の場合 { 新しいBindingExceptionをスローします("メソッド" + command.getName() + "@ResultMap アノテーションまたは @ResultType アノテーションのいずれかが必要です。" + " または XML の resultType 属性を使用して、ResultHandler をパラメーターとして使用できるようにします。"); } オブジェクト param = method.convertArgsToSqlCommandParam(args); (method.hasRowBounds()) の場合 { RowBounds rowBounds = method.extractRowBounds(args); sqlSession.select(command.getName(), param, rowBounds, method.extractResultHandler(args)); } それ以外 { sqlSession.select(command.getName(), param, method.extractResultHandler(args)); } } プライベート <E> オブジェクト executeForMany(SqlSession sqlSession, オブジェクト[] args) { リスト<E> 結果; オブジェクト param = method.convertArgsToSqlCommandParam(args); (method.hasRowBounds()) の場合 { RowBounds rowBounds = method.extractRowBounds(args); 結果 = sqlSession.<E>selectList(command.getName(), param, rowBounds); } それ以外 { 結果 = sqlSession.<E>selectList(command.getName(), param); } // 問題 #510 コレクションと配列のサポート (!method.getReturnType().isAssignableFrom(result.getClass()))の場合{ メソッド.getReturnType().isArray() の場合 { convertToArray(結果)を返します。 } それ以外 { convertToDeclaredCollection(sqlSession.getConfiguration(), 結果) を返します。 } } 結果を返します。 } プライベート <T> Cursor<T> executeForCursor(SqlSession sqlSession, Object[] args) { カーソル<T>結果; オブジェクト param = method.convertArgsToSqlCommandParam(args); (method.hasRowBounds()) の場合 { RowBounds rowBounds = method.extractRowBounds(args); 結果 = sqlSession.<T>selectCursor(command.getName(), param, rowBounds); } それ以外 { 結果 = sqlSession.<T>selectCursor(command.getName(), param); } 結果を返します。 } プライベート <E> オブジェクト convertToDeclaredCollection(構成 config、List<E> list) { オブジェクトコレクション = config.getObjectFactory().create(method.getReturnType()); MetaObject metaObject = config.newMetaObject(コレクション); metaObject.addAll(リスト); コレクションを返却する。 } @SuppressWarnings("チェックなし") プライベート <E> オブジェクト convertToArray(List<E> リスト) { クラス<?> arrayComponentType = method.getReturnType().getComponentType(); オブジェクト配列 = Array.newInstance(arrayComponentType, list.size()); 配列コンポーネントタイプがプリミティブである場合 (int i = 0; i < list.size(); i++) の場合 { 配列.set(配列、i、リスト.get(i)); } 配列を返します。 } それ以外 { list.toArray((E[])配列)を返します。 } } プライベート <K, V> Map<K, V> executeForMap(SqlSession sqlSession, Object[] args) { Map<K, V> 結果; オブジェクト param = method.convertArgsToSqlCommandParam(args); (method.hasRowBounds()) の場合 { RowBounds rowBounds = method.extractRowBounds(args); 結果 = sqlSession.<K, V>selectMap(command.getName(), param, method.getMapKey(), rowBounds); } それ以外 { 結果 = sqlSession.<K, V>selectMap(command.getName(), param, method.getMapKey()); } 結果を返します。 } パブリック静的クラス ParamMap<V> は HashMap<String, V> を拡張します { プライベート静的最終ロングシリアルバージョンUID = -2212268410512043556L; @オーバーライド パブリック V get(オブジェクトキー) { (!super.containsKey(キー))の場合{ throw new BindingException("パラメータ '" + key + "' が見つかりません。使用可能なパラメータは " + keySet() です); } super.get(キー) を返します。 } } パブリック静的クラス SqlCommand { プライベート最終文字列名; プライベート最終 SqlCommandType 型; パブリック SqlCommand(構成構成、クラス<?> mapperInterface、メソッドメソッド) { 最終的な文字列 methodName = method.getName(); 最終的な Class<?> 宣言クラス = method.getDeclaringClass(); MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass, 構成); ms == nullの場合{ メソッドのgetAnnotation(Flush.class)がnullの場合 名前 = null; タイプ = SqlCommandType.FLUSH; } それ以外 { throw new BindingException("無効なバインドされたステートメント (見つかりません): " + mapperInterface.getName() + "." + メソッド名); } } それ以外 { 名前 = ms.getId(); タイプ = ms.getSqlCommandType(); (型 == SqlCommandType.UNKNOWN) の場合 { throw new BindingException("不明な実行メソッド: " + name); } } } パブリック文字列getName() { 名前を返します。 } パブリックSqlCommandType getType() { 戻り値の型; } プライベートMappedStatementresolveMappedStatement(Class<?> mapperInterface、String methodName、 クラス<?> 宣言クラス、構成構成) { 文字列 statementId = mapperInterface.getName() + "." + methodName; if (configuration.hasStatement(statementId)) { configuration.getMappedStatement(statementId) を返します。 } そうでない場合 (mapperInterface.equals(宣言クラス)) { null を返します。 } (クラス<?> superInterface の場合: mapperInterface.getInterfaces()) { if (宣言クラスがスーパーインターフェースから割り当て可能) { MappedStatement ms = resolveMappedStatement(スーパーインターフェース、メソッド名、 宣言クラス、構成); ms != null の場合 { ms を返します。 } } } null を返します。 } } パブリック静的クラスMethodSignature{ プライベート最終ブール値 returnsMany; プライベート最終ブール値 returnsMap; プライベート最終ブール値はVoidを返します。 プライベート最終ブール値 returnsCursor; プライベート最終クラス<?> returnType; プライベート最終文字列 mapKey; プライベート最終整数 resultHandlerIndex; プライベート最終整数 rowBoundsIndex; プライベート最終 ParamNameResolver paramNameResolver; パブリック メソッド シグネチャ (構成構成、クラス <?> マッパー インターフェース、メソッド メソッド) { 型resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface); if (resolvedReturnType のインスタンス Class<?>) { this.returnType = (クラス<?>) 解決されたReturnType; } そうでない場合 (resolvedReturnType が ParameterizedType のインスタンスである場合) { this.returnType = (クラス<?>) ((パラメータ化された型) 解決されたReturnType).getRawType(); } それ以外 { メソッドの returnType を取得します。 } this.returnsVoid = void.class.equals(this.returnType); this.returnsMany = (configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray()); this.returnsCursor = Cursor.class.equals(this.returnType); this.mapKey = getMapKey(メソッド); this.returnsMap = (this.mapKey != null); this.rowBoundsIndex = getUniqueParamIndex(メソッド、RowBounds.class); this.resultHandlerIndex = getUniqueParamIndex(メソッド、ResultHandler.class); this.paramNameResolver = 新しい ParamNameResolver(構成、メソッド); } パブリックオブジェクトconvertArgsToSqlCommandParam(Object[] args) { paramNameResolver.getNamedParams(args) を返します。 } パブリックブール値hasRowBounds() { rowBoundsIndex != null を返します。 } パブリック RowBounds extractRowBounds(Object[] args) { hasRowBounds() を返します。(RowBounds) args[rowBoundsIndex] : null; } パブリックブール型hasResultHandler() { resultHandlerIndex != null を返します。 } パブリックResultHandler extractResultHandler(Object[] args) { hasResultHandler() を返します? (ResultHandler) args[resultHandlerIndex] : null; } パブリック文字列 getMapKey() { mapKey を返します。 } パブリッククラス<?> getReturnType() { returnType を返します。 } パブリックブール値 returnsMany() { 戻り値 returnsMany; } パブリックブール値returnsMap() { returnsMap を返します。 } パブリックブール値 returnsVoid() { 戻り値はVoidです。 } パブリックブール値 returnsCursor() { 戻り値 returnsCursor; } プライベートInteger getUniqueParamIndex(メソッド method, Class<?> paramType) { 整数インデックス = null; 最終的な Class<?>[] argTypes = method.getParameterTypes(); (int i = 0; i < argTypes.length; i++) { (paramType.isAssignableFrom(argTypes[i])の場合){ インデックスが null の場合 インデックス = i; } それ以外 { throw new BindingException(method.getName() + " は複数の " + paramType.getSimpleName() + " パラメータを持つことはできません"); } } } インデックスを返します。 } プライベート文字列getMapKey(メソッドメソッド) { 文字列 mapKey = null; Map.class.isAssignableFrom(method.getReturnType()) の場合 { 最終的な MapKey mapKeyAnnotation = method.getAnnotation(MapKey.class); mapKeyAnnotation が null の場合 mapKey = mapKeyAnnotation.value(); } } mapKey を返します。 } } 以上がこの記事の全内容です。皆様の勉強のお役に立てれば幸いです。また、123WORDPRESS.COM を応援していただければ幸いです。 以下もご興味があるかもしれません:
|
<<: Vue3.0 は虫眼鏡効果のケーススタディを実装します
>>: Linux の PHP に XML 拡張機能をインストールする詳細な手順
プロジェクトでは https サービスを使用する必要があるため、Alibaba Cloud では無料...
目次1. コンポーネント切り替え方式方法1: v-ifとv-elseを使用する方法 2: 組み込みコ...
Docker コンテナは互いに分離されており、相互にアクセスできないことは誰もが知っていますが、依存...
MySql は、私たちが頻繁に使用するデータ ソースです。開発者が練習、小規模なプライベート ゲーム...
目次ケースシナリオ問題を解決するまとめケースシナリオ本日、オンラインで問題が発見されました。監視範囲...
関連記事:初心者が学ぶ HTML タグ (3)導入された HTML タグは、必ずしも XHTML 仕...
目次1. データベースを理解する1.1 データベースとデータ構造の関係1.2 なぜデータベースが必要...
パノラマビュー効果を見てみましょう: 住所を表示スクリーンショット: 体験してみると、周囲の環境がぐ...
1. 遷移属性の理解1. transition 属性は、次の 4 つの遷移プロパティを設定するために...
目次序文RMの後には希望はあるのでしょうか?最前線を使ってファイルを取得するextundeleteを...
ウェブサイトのナビゲーションを設計することは、家の基礎を築くようなものです。基礎がしっかりしていなけ...
データベースは、どのオブジェクトにどのフィールドが含まれているかを照会します。 *を選択 sysob...
目次1. 文法2. 例3. その他の関連方法長い間、reduce() メソッドの具体的な使い方を理解...
トリガーの紹介トリガーは、テーブルに関連付けられた特別なストアド プロシージャであり、テーブル内のデ...
環境ホスト名IPアドレス仕えるプロメテウス192.168.237.137プロメテウス、グラファナノー...