ClusterJ - рдЬрд╛рд╡рд╛ рд╕реЗ MySQL NDB рдХреНрд▓рд╕реНрдЯрд░ рдХреЗ рд╕рд╛рде рдХрд╛рдо рдХрд░рдирд╛

рдирдорд╕реНрдХрд╛рд░, рд╣реЗрдмреНрд░! рдЗрд╕ рд▓реЗрдЦ рдореЗрдВ рдореИрдВ рдЬрд╛рд╡рд╛ рдХреЗ рд▓рд┐рдП рдПрдХ рд▓рд╛рдЗрдмреНрд░реЗрд░реА рдкрд░ рд╡рд┐рдЪрд╛рд░ рдХрд░рдирд╛ рдЪрд╛рд╣рддрд╛ рд╣реВрдВ рдЬреИрд╕реЗ рдХрд┐ ClusterJ , рдЬреЛ Java рдХреЛрдб рд╕реЗ MySQL NDBCLUSTER рдЗрдВрдЬрди рдХреЗ рд╕рд╛рде рдХрд╛рдо рдХрд░рдирд╛ рдмрд╣реБрдд рдЖрд╕рд╛рди рдмрдирд╛рддрд╛ рд╣реИ, рдЬреЛ рдХрд┐ JPA рдФрд░ Hibernate рдЕрд╡рдзрд╛рд░рдгрд╛ рдХреЗ рд╕рдорд╛рди рдПрдХ рдЙрдЪреНрдЪ-рд╕реНрддрд░реАрдп рдПрдкреАрдЖрдИ рд╣реИред


рдЗрд╕ рд▓реЗрдЦ рдХреЗ рдврд╛рдВрдЪреЗ рдореЗрдВ, рд╣рдо SpringBoot рдкрд░ рдПрдХ рд╕рд░рд▓ рдПрдкреНрд▓рд┐рдХреЗрд╢рди рдмрдирд╛рдПрдВрдЧреЗ, рдФрд░ рдСрдЯреЛрдХреЙрдиреНрдлрд╝рд┐рдЧрд░реЗрд╢рди рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ рдЕрдиреБрдкреНрд░рдпреЛрдЧреЛрдВ рдореЗрдВ рд╕реБрд╡рд┐рдзрд╛рдЬрдирдХ рдЙрдкрдпреЛрдЧ рдХреЗ рд▓рд┐рдП рдмреЛрд░реНрдб рдкрд░ ClusterJ рд╕рд╛рде рдПрдХ рд╕реНрдЯрд╛рд░реНрдЯрд░ рднреА рдмрдирд╛рдПрдВрдЧреЗред рд╣рдо JUnit5 рдФрд░ TestContainers рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ рд╕рд░рд▓ рдкрд░реАрдХреНрд╖рдг TestContainers , рдЬреЛ рдПрдкреАрдЖрдИ рдХреЗ рдореВрд▓ рдЙрдкрдпреЛрдЧ рдХреЛ рджрд┐рдЦрд╛рдПрдЧрд╛ред
рдореИрдВ рдХрдИ рдХрдорд┐рдпреЛрдВ рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рднреА рдмрд╛рдд рдХрд░реВрдВрдЧрд╛ рдЬреЛ рдореБрдЭреЗ рдЙрд╕рдХреЗ рд╕рд╛рде рдХрд╛рдо рдХрд░рдиреЗ рдХреА рдкреНрд░рдХреНрд░рд┐рдпрд╛ рдореЗрдВ рд╕рд╛рдордирд╛ рдХрд░рдирд╛ рдкрдбрд╝рд╛ рдерд╛ред


рдХреМрди рдкрд░рд╡рд╛рд╣ рдХрд░рддрд╛ рд╣реИ, рдмрд┐рд▓реНрд▓реА рдореЗрдВ рдЖрдкрдХрд╛ рд╕реНрд╡рд╛рдЧрдд рд╣реИред


рдкрд░рд┐рдЪрдп


MySQL NDB Cluster рд╕рдХреНрд░рд┐рдп рд░реВрдк рд╕реЗ рдХрд╛рдо рдореЗрдВ рдЙрдкрдпреЛрдЧ рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИ, рдФрд░ рдПрдХ рдкрд░рд┐рдпреЛрдЬрдирд╛ рдореЗрдВ, рдЧрддрд┐ рдХреЗ рд▓рд┐рдП, рдХрд╛рд░реНрдп рд╕рд╛рдорд╛рдиреНрдп JDBC рдмрдЬрд╛рдп ClusterJ рдкреБрд╕реНрддрдХрд╛рд▓рдп рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдирд╛ рдерд╛, рдЬреЛ рдХрд┐ рдЗрд╕рдХреЗ API рдореЗрдВ JPA рд╕рдорд╛рди рд╣реИ, рдФрд░ рд╡рд╛рд╕реНрддрд╡ рдореЗрдВ, libndbclient.so рд▓рд╛рдЗрдмреНрд░реЗрд░реА рдкрд░ рдПрдХ рдЖрд╡рд░рдг рд╣реИ рдЬреЛ рдЗрд╕реЗ JNI рдорд╛рдзреНрдпрдо рд╕реЗ рдЙрдкрдпреЛрдЧ рдХрд░рддрд╛ рд╣реИред


рдЬреЛ рд▓реЛрдЧ рдирд╣реАрдВ рдЬрд╛рдирддреЗ рд╣реИрдВ, рдЙрдирдХреЗ рд▓рд┐рдП MySQL NDB рдХреНрд▓рд╕реНрдЯрд░ рдПрдХ рд╡рд┐рддрд░рд┐рдд рдХрдВрдкреНрдпреВрдЯрд┐рдВрдЧ рд╡рд╛рддрд╛рд╡рд░рдг рдХреЗ рд▓рд┐рдП рдЕрдиреБрдХреВрд▓рд┐рдд рдПрдХ рдЕрддреНрдпрдзрд┐рдХ рд╕реБрд▓рдн рдФрд░ рдирд┐рд░рд░реНрдердХ MySQL рд╕рдВрд╕реНрдХрд░рдг рд╣реИ рдЬреЛ рдХреНрд▓рд╕реНрдЯрд░ рдореЗрдВ рд╕рдВрдЪрд╛рд▓рд┐рдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП NDB рднрдВрдбрд╛рд░рдг NDB ( NDBCLUSTER ) рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рддрд╛ рд╣реИред рдореИрдВ рдпрд╣рд╛рдБ рдЗрд╕ рдкрд░ рд╡рд┐рд╕реНрддрд╛рд░ рд╕реЗ рдзреНрдпрд╛рди рдирд╣реАрдВ рджреЗрдирд╛ рдЪрд╛рд╣рддрд╛, рдЖрдк рдпрд╣рд╛рдБ рдФрд░ рдпрд╣рд╛рдБ рдкрдврд╝ рд╕рдХрддреЗ рд╣реИрдВ

рдЗрд╕ рдбреЗрдЯрд╛рдмреЗрд╕ рдХреЗ рд╕рд╛рде рдЬрд╛рд╡рд╛ рдХреЛрдб рд╕реЗ рдХрд╛рдо рдХрд░рдиреЗ рдХреЗ рджреЛ рддрд░реАрдХреЗ рд╣реИрдВ:


  • JDBC рдФрд░ SQL рдкреНрд░рд╢реНрдиреЛрдВ рдХреЗ рдорд╛рдзреНрдпрдо рд╕реЗ рдорд╛рдирдХ
  • MySQL Cluster рдореЗрдВ рдЙрдЪреНрдЪ-рдкреНрд░рджрд░реНрд╢рди рдбреЗрдЯрд╛ рдПрдХреНрд╕реЗрд╕ рдХреЗ рд▓рд┐рдП Via ClusterJ ред

рдЫрд╡рд┐


ClusterJ 4 рдореБрдЦреНрдп рдЕрд╡рдзрд╛рд░рдгрд╛рдУрдВ рдХреЗ рдЖрд╕рдкрд╛рд╕ рдмрдирд╛рдпрд╛ рдЧрдпрд╛ рд╣реИ:


  • SessionFactory - рдХрдиреЗрдХреНрд╢рди рдкреВрд▓ рдХрд╛ рдПрдХ рдПрдирд╛рд▓реЙрдЧ, рдЬрд┐рд╕рдХрд╛ рдЙрдкрдпреЛрдЧ рд╕рддреНрд░ рдкреНрд░рд╛рдкреНрдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИред рдХреНрд▓рд╕реНрдЯрд░ рдХреЗ рдкреНрд░рддреНрдпреЗрдХ рдЙрджрд╛рд╣рд░рдг рдХрд╛ рдЕрдкрдирд╛ рд╕рддреНрд░ рд╕рддреНрд░ рд╣реЛрдирд╛ рдЪрд╛рд╣рд┐рдПред
  • Session - рдПрдХ MySQL рдХреНрд▓рд╕реНрдЯрд░ рдХреЗ рд▓рд┐рдП рдПрдХ рд╕реАрдзрд╛ рд╕рдВрдмрдВрдз рд╣реИред
  • Domain Object - рдПрдХ рдПрдиреЛрдЯреЗрдЯ рдЗрдВрдЯрд░рдлрд╝реЗрд╕ рдЬреЛ JPA рд╕рдорд╛рди Java рдХреЛрдб рдореЗрдВ рддрд╛рд▓рд┐рдХрд╛ рдХреЗ рдорд╛рдирдЪрд┐рддреНрд░рдг рдХрд╛ рдкреНрд░рддрд┐рдирд┐рдзрд┐рддреНрд╡ JPA ред
  • Transaction - рдХрд╛рд░реНрдп рдХреА рдПрдХ рдкрд░рдорд╛рдгреБ рдЗрдХрд╛рдИ рд╣реИред рдХрд┐рд╕реА рднреА рд╕рдордп, рдПрдХ рд╕рддреНрд░ рдореЗрдВ, рдПрдХ рд▓реЗрдирджреЗрди рдирд┐рд╖реНрдкрд╛рджрд┐рдд рд╣реЛрддрд╛ рд╣реИред рдХреЛрдИ рднреА рдСрдкрд░реЗрд╢рди (рдкреНрд░рд╛рдкреНрдд рдХрд░реЗрдВ, рдбрд╛рд▓реЗрдВ, рдЕрдкрдбреЗрдЯ рдХрд░реЗрдВ, рд╣рдЯрд╛рдПрдВ) рдПрдХ рдирдП рд▓реЗрдирджреЗрди рдореЗрдВ рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИред

рдХреНрд▓рд╕реНрдЯрд░ рд╕реАрдорд╛рдПрдБ:


  • рдЬреЙрдЗрди рдХрд╛ рдЕрднрд╛рд╡
  • рдЯреЗрдмрд▓ рдФрд░ рдЗрдВрдбреЗрдХреНрд╕ рдмрдирд╛рдиреЗ рдХрд╛ рдХреЛрдИ рддрд░реАрдХрд╛ рдирд╣реАрдВ рд╣реИред рдРрд╕рд╛ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, JDBC рдЙрдкрдпреЛрдЧ рдХрд░реЗрдВред
  • рдХреЛрдИ рд╡рд┐рд▓рдВрдмрд┐рдд рд▓реЛрдбрд┐рдВрдЧ ( Lazy ) рдирд╣реАрдВред рдПрдХ рдмрд╛рд░ рдореЗрдВ рдкреВрд░рд╛ рд░рд┐рдХреЙрд░реНрдб рдбрд╛рдЙрдирд▓реЛрдб рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИред
  • рдбреЛрдореЗрди рдСрдмреНрдЬреЗрдХреНрдЯ рдореЗрдВ, рддрд╛рд▓рд┐рдХрд╛рдУрдВ рдХреЗ рдмреАрдЪ рд╕рдВрдмрдВрдзреЛрдВ рдХреЛ рдкрд░рд┐рднрд╛рд╖рд┐рдд рдХрд░рдирд╛ рд╕рдВрднрд╡ рдирд╣реАрдВ рд╣реИред OneToMany , ManyToOne , OneToMany рдХреА рд╕рдорд╛рдирддрд╛ рдкреВрд░реА рддрд░рд╣ рд╕реЗ рдЕрдиреБрдкрд╕реНрдерд┐рдд рд╣реИред

рдЕрднреНрдпрд╛рд╕ред рдмрд╛рдд рд╕рд╕реНрддреА рд╣реИред рдореБрдЭреЗ рдХреЛрдб рджрд┐рдЦрд╛рдУред


рдареАрдХ рд╣реИ, рдкрд░реНрдпрд╛рдкреНрдд рд╕рд┐рджреНрдзрд╛рдВрдд, рдЪрд▓рд┐рдП рдЕрднреНрдпрд╛рд╕ рдХрд░рддреЗ рд╣реИрдВред


рд╕рд╛рдордирд╛ рдХрд░рдиреЗ рд╡рд╛рд▓реА рдкрд╣рд▓реА рд╕рдорд╕реНрдпрд╛ рдХреЗрдВрджреНрд░реАрдп рдорд╛рд╡реЗрди рд░рд┐рдкреЙрдЬрд┐рдЯрд░реА рдореЗрдВ ClusterJ рдХреА рдХрдореА рд╣реИред рд╕реНрдерд╛рдиреАрдп рд░рд┐рдкреЙрдЬрд┐рдЯрд░реА рдореЗрдВ рдкреЗрди рдХреЗ рд╕рд╛рде рд▓рд╛рдЗрдмреНрд░реЗрд░реА рд╕реНрдерд╛рдкрд┐рдд рдХрд░реЗрдВред рдпрд╣ рд╕реНрдкрд╖реНрдЯ рд╣реИ рдХрд┐ рдЕрдЪреНрдЫреЗ рдХреЗ рд▓рд┐рдП, рдпрд╣ Nexus рдпрд╛ рдХреБрдЫ Artifactory рдореЗрдВ рдЭреВрда рд╣реЛрдирд╛ рдЪрд╛рд╣рд┐рдП, рд▓реЗрдХрд┐рди рд╣рдорд╛рд░реЗ рдЙрджрд╛рд╣рд░рдг рдХреЗ рд▓рд┐рдП рдпрд╣ рдЕрдирд╛рд╡рд╢реНрдпрдХ рд╣реИред


рдЗрд╕рд▓рд┐рдП, рдпрд╣рд╛рдВ рдЬрд╛рдПрдВ рдФрд░ рдЕрдкрдирд╛ рдСрдкрд░реЗрдЯрд┐рдВрдЧ рд╕рд┐рд╕реНрдЯрдо рдЪреБрдиреЗрдВред рдпрджрд┐ рдЖрдк рдПрдХ Linux OS рдкрд░ рд╣реИрдВ, рддреЛ Linux mysql-cluster-community-java рдирд╛рдордХ рдкреИрдХреЗрдЬ рдбрд╛рдЙрдирд▓реЛрдб рдХрд░реЗрдВ рдФрд░ рдЗрд╕ rpm / рдбрд┐рдм рдкреИрдХреЗрдЬ рдХреЛ рд╕реНрдерд╛рдкрд┐рдд рдХрд░реЗрдВред рдпрджрд┐ рдЖрдкрдХреЗ рдкрд╛рд╕ Windows , рддреЛ рдкреВрд░реНрдг mysql-cluster-gp рд╕рдВрдЧреНрд░рд╣ рдбрд╛рдЙрдирд▓реЛрдб рдХрд░реЗрдВред


рдПрдХ рддрд░рд╣ рд╕реЗ рдпрд╛ рдХрд┐рд╕реА рдЕрдиреНрдп, рд╣рдорд╛рд░реЗ рдкрд╛рд╕ рдлрд╝реЙрд░реНрдо рдХреА рдПрдХ рдЬрд╛рд░ рдлрд╝рд╛рдЗрд▓ рд╣реЛрдЧреА: clusterj-{version}.jar ред рд╣рдо рдЗрд╕реЗ maven рдорд╛рдзреНрдпрдо рд╕реЗ рдбрд╛рд▓рддреЗ рд╣реИрдВ:


 mvn install:install-file -DgroupId=com.mysql.ndb -DartifactId=clusterj -Dversion={version} -Dpackaging=jar -Dfile=clusterj-{version}.jar -DgeneratePom=true 

рд╣рдореЗрдВ libndbclient рд▓рд╛рдЗрдмреНрд░реЗрд░реА рдХреА рднреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИ, рдЬреЛ NDB API рд╕рд╛рде рдХрд╛рдо рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП C++ рдлрд╝рдВрдХреНрд╢рдВрд╕ рдХрд╛ рдПрдХ рд╕реЗрдЯ рд╣реИ, рдЬрд┐рд╕реЗ ClusterJ рдорд╛рдзреНрдпрдо рд╕реЗ рдХреЙрд▓ рдХрд░рддрд╛ рд╣реИред Windows рдпрд╣ рд▓рд╛рдЗрдмреНрд░реЗрд░реА (.dll) ndbclient_{version} mysql-cluster-gp рд╕рдВрдЧреНрд░рд╣ рдореЗрдВ рд╣реИ, Linux рдЖрдкрдХреЛ ndbclient_{version} рдкреИрдХреЗрдЬ рдбрд╛рдЙрдирд▓реЛрдб рдХрд░рдирд╛ ndbclient_{version} ред


рдЗрд╕рдХреЗ рдмрд╛рдж, рдПрдХ рдкреНрд░реЛрдЬреЗрдХреНрдЯ рдмрдирд╛рдПрдВред рд╣рдо рдкрд░реАрдХреНрд╖рдг рдХреЗ рд▓рд┐рдП SpringBoot , JUnit5 + TestContainers рдЙрдкрдпреЛрдЧ рдХрд░реЗрдВрдЧреЗред


рдкрд░рд┐рдпреЛрдЬрдирд╛ рдХреА рдЕрдВрддрд┐рдо рд╕рдВрд░рдЪрдирд╛


рдкрд░рд┐рдпреЛрдЬрдирд╛ рдореЗрдВ рджреЛ рдореЙрдбреНрдпреВрд▓ рд╢рд╛рдорд┐рд▓ рд╣реИрдВ:


  • clusterj-spring-boot-starter рдПрдХ рд╕реНрдЯрд╛рд░реНрдЯрд░ рд╣реИ рдЬрд┐рд╕рдореЗрдВ ClusterJ рд╢рд╛рдорд┐рд▓ ClusterJ , рд╕рд╛рде рд╣реА рдПрдЯрдХреЛрдирдлрд┐рдЧрд░реЗрд╢рди рднреА рд╣реИред рдЗрд╕ рд╕реНрдЯрд╛рд░реНрдЯрд░ рдХреА рдмрджреМрд▓рдд, рд╣рдо рд╣рдорд╛рд░реЗ appliation.yml рдлрд╝рд╛рдЗрд▓ рдореЗрдВ MySQL NDB рдХреЗ рдХрдиреЗрдХреНрд╢рди рдХрд╛ рд╡рд░реНрдгрди рдЗрд╕ рдкреНрд░рдХрд╛рд░ рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ:

 clusterj: connectString: localhost:1186 dataBaseName: NDB_DB 

рдЙрд╕рдХреЗ рдмрд╛рдж, SpringBoot рд╣рдорд╛рд░реЗ рд▓рд┐рдП рдХрдиреЗрдХреНрд╢рди рдХреЗ рд▓рд┐рдП рдЖрд╡рд╢реНрдпрдХ SessionFactory рдХрд╛рд░рдЦрд╛рдирд╛ рдмрдирд╛ рджреЗрдЧрд╛ред


  • clusterj-app рд╣реА рдПрдкреНрд▓рд┐рдХреЗрд╢рди рд╣реИ, рдЬрд┐рд╕реЗ рд╣рдорд╛рд░рд╛ рд╕реНрдЯрд╛рд░реНрдЯрд░ рдЙрдкрдпреЛрдЧ рдХрд░реЗрдЧрд╛ред рдЖрдЗрдП рд╣рдо рдЗрд╕ рдкрд░ рдЕрдзрд┐рдХ рд╡рд┐рд╕реНрддрд╛рд░ рд╕реЗ рдзреНрдпрд╛рди рджреЗрдВред

рдЖрд░рдВрдн рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рд╣рдореЗрдВ JPA рддрд░рд╣ рдПрдХ рдбреЛрдореЗрди рдореЙрдбрд▓ рдмрдирд╛рдирд╛ рд╣реЛрдЧрд╛ред рдХреЗрд╡рд▓ рдЗрд╕ рдорд╛рдорд▓реЗ рдореЗрдВ рд╣рдореЗрдВ рдЗрд╕реЗ рдПрдХ рдЗрдВрдЯрд░рдлрд╝реЗрд╕ рдХреЗ рд░реВрдк рдореЗрдВ рдХрд░рдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИ, рдЬрд┐рд╕рдореЗрдВ clusterj рдХрд╛ рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди clusterj рдЬрд╛рдПрдЧрд╛:


 import com.mysql.clusterj.annotation.Column; import com.mysql.clusterj.annotation.PersistenceCapable; import com.mysql.clusterj.annotation.PrimaryKey; @PersistenceCapable(table = "user") public interface User { @PrimaryKey int getId(); void setId(int id); @Column(name = "firstName") String getFirstName(); void setFirstName(String firstName); @Column(name = "lastName") String getLastName(); void setLastName(String lastName); } 

рдЕрднреА рдПрдХ рд╕рдорд╕реНрдпрд╛ рд╣реИред PersistenceCapable рдПрдиреЛрдЯреЗрд╢рди рдореЗрдВ рд╕реНрдХреАрдорд╛ рдпрд╛ рдбреЗрдЯрд╛рдмреЗрд╕ рдХрд╛ рдирд╛рдо рдирд┐рд░реНрджрд┐рд╖реНрдЯ рдХрд░рдиреЗ рдХреА рдХреНрд╖рдорддрд╛ рд╣реИ рдЬрд┐рд╕рдореЗрдВ рддрд╛рд▓рд┐рдХрд╛ рдирд┐рд╣рд┐рдд рд╣реИ, рд╣рд╛рд▓рд╛рдВрдХрд┐ рдпрд╣ рдХрд╛рдо рдирд╣реАрдВ рдХрд░рддрд╛ рд╣реИред рдирд┐рд╢реНрдЪрд┐рдд рд░реВрдк рд╕реЗред ClusterJ рдЗрд╕реЗ рд▓рд╛рдЧреВ рдирд╣реАрдВ рдХрд┐рдпрд╛ рдЧрдпрд╛ рд╣реИред рдЗрд╕рд▓рд┐рдП, ClusterJ рдорд╛рдзреНрдпрдо рд╕реЗ рдХрд╛рдо рдХрд░ рд░рд╣реЗ рд╕рднреА рддрд╛рд▓рд┐рдХрд╛рдУрдВ рдХреЛ рдПрдХ рд╣реА рд╕реНрдХреАрдорд╛ рдореЗрдВ рд╣реЛрдирд╛ рдЪрд╛рд╣рд┐рдП, рдЬрд┐рд╕рдХреЗ рдкрд░рд┐рдгрд╛рдорд╕реНрд╡рд░реВрдк рддрд╛рд▓рд┐рдХрд╛рдУрдВ рдХрд╛ рдПрдХ рдбрдВрдк рд╣реЛрддрд╛ рд╣реИ, рдЬреЛ рддрд╛рд░реНрдХрд┐рдХ рд░реВрдк рд╕реЗ рд╡рд┐рднрд┐рдиреНрди ClusterJ рд╣реЛрдирд╛ рдЪрд╛рд╣рд┐рдПред


рдЕрдм рдЗрд╕ рдЗрдВрдЯрд░рдлрд╝реЗрд╕ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдиреЗ рдХрд╛ рдкреНрд░рдпрд╛рд╕ рдХрд░рддреЗ рд╣реИрдВред рдРрд╕рд╛ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рд╣рдо рдПрдХ рд╕рд╛рдзрд╛рд░рдг рдкрд░реАрдХреНрд╖рдг рд▓рд┐рдЦрддреЗ рд╣реИрдВред


MySQL Cluster рд╕реНрдерд╛рдкрд┐рдд рдХрд░рдиреЗ рд╕реЗ рдкрд░реЗрд╢рд╛рди рдирд╣реАрдВ рд╣реЛрдиреЗ рдХреЗ рд▓рд┐рдП, рд╣рдо рдПрдХреАрдХрд░рдг рдкрд░реАрдХреНрд╖рдг TestContainers рдФрд░ Docker рдХреЗ рд▓рд┐рдП рдЕрджреНрднреБрдд рдкреБрд╕реНрддрдХрд╛рд▓рдп рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░реЗрдВрдЧреЗред рдЪреВрдВрдХрд┐ рд╣рдо JUnit5 рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░ рд░рд╣реЗ рд╣реИрдВ, рд╣рдо рдПрдХ рд╕рд░рд▓ Extension рд▓рд┐рдЦреЗрдВрдЧреЗ:


рдПрдХреНрд╕рдЯреЗрдВрд╢рди рд╕реЛрд░реНрд╕ рдХреЛрдб
 import com.github.dockerjava.api.model.Network; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.extension.Extension; import org.testcontainers.containers.BindMode; import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.wait.strategy.Wait; import org.testcontainers.shaded.com.google.common.collect.ImmutableMap; import java.time.Duration; import java.util.stream.Stream; @Slf4j class MySQLClusterTcExtension implements Extension { private static final String MYSQL_USER = "sys"; private static final String MYSQL_PASSWORD = "qwerty"; private static final String CLUSTERJ_DATABASE = "NDB_DB"; private static Network.Ipam getIpam() { Network.Ipam ipam = new Network.Ipam(); ipam.withDriver("default"); Network.Ipam.Config config = new Network.Ipam.Config(); config.withSubnet("192.168.0.0/16"); ipam.withConfig(config); return ipam; } private static org.testcontainers.containers.Network network = org.testcontainers.containers.Network.builder() .createNetworkCmdModifier(createNetworkCmd -> createNetworkCmd.withIpam(getIpam())) .build(); private static GenericContainer ndbMgmd = new GenericContainer<>("mysql/mysql-cluster") .withNetwork(network) .withClasspathResourceMapping("mysql-cluster.cnf", "/etc/mysql-cluster.cnf", BindMode.READ_ONLY) .withClasspathResourceMapping("my.cnf", "/etc/my.cnf", BindMode.READ_ONLY) .withCreateContainerCmdModifier(createContainerCmd -> createContainerCmd.withIpv4Address("192.168.0.2")) .withCommand("ndb_mgmd") .withExposedPorts(1186) .waitingFor(Wait.forListeningPort().withStartupTimeout(Duration.ofSeconds(150))); private static GenericContainer ndbd1 = new GenericContainer<>("mysql/mysql-cluster") .withNetwork(network) .withClasspathResourceMapping("mysql-cluster.cnf", "/etc/mysql-cluster.cnf", BindMode.READ_ONLY) .withClasspathResourceMapping("my.cnf", "/etc/my.cnf", BindMode.READ_ONLY) .withCreateContainerCmdModifier(createContainerCmd -> createContainerCmd.withIpv4Address("192.168.0.3")) .withCommand("ndbd"); private static GenericContainer ndbMysqld = new GenericContainer<>("mysql/mysql-cluster") .withNetwork(network) .withCommand("mysqld") .withCreateContainerCmdModifier(createContainerCmd -> createContainerCmd.withIpv4Address("192.168.0.10")) .withClasspathResourceMapping("mysql-cluster.cnf", "/etc/mysql-cluster.cnf", BindMode.READ_ONLY) .withClasspathResourceMapping("my.cnf", "/etc/my.cnf", BindMode.READ_ONLY) .waitingFor(Wait.forListeningPort()) .withEnv(ImmutableMap.of("MYSQL_DATABASE", CLUSTERJ_DATABASE, "MYSQL_USER", MYSQL_USER, "MYSQL_PASSWORD", MYSQL_PASSWORD)) .withExposedPorts(3306) .waitingFor(Wait.forListeningPort()); static { log.info("Start MySQL Cluster testcontainers extension...\n"); Stream.of(ndbMgmd, ndbd1, ndbMysqld).forEach(GenericContainer::start); String ndbUrl = ndbMgmd.getContainerIpAddress() + ":" + ndbMgmd.getMappedPort(1186); String mysqlUrl = ndbMysqld.getContainerIpAddress() + ":" + ndbMysqld.getMappedPort(3306); String mysqlConnectionString = "jdbc:mysql://" + mysqlUrl + "/" + CLUSTERJ_DATABASE + "?useUnicode=true" + "&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&useSSL=false"; System.setProperty("clusterj.connectString", ndbUrl); System.setProperty("clusterj.dataBaseName", CLUSTERJ_DATABASE); System.setProperty("spring.datasource.username", MYSQL_USER); System.setProperty("spring.datasource.password", MYSQL_PASSWORD); System.setProperty("spring.datasource.url", mysqlConnectionString); } } 

рдЗрд╕ рдПрдХреНрд╕рдЯреЗрдВрд╢рди рдореЗрдВ, рд╣рдо рдХреНрд▓рд╕реНрдЯрд░ рдХреЗ рдХрдВрдЯреНрд░реЛрд▓ рдиреЛрдб, рдиреЛрдб рдХреЗ рд▓рд┐рдП рдПрдХ рддрд┐рдерд┐ рдФрд░ MySQL рдиреЛрдб рдмрдврд╝рд╛рддреЗ рд╣реИрдВред рдЙрд╕рдХреЗ рдмрд╛рдж, рд╣рдордиреЗ рд╕реНрдкреНрд░рд┐рдВрдЧрдмреВрдЯ рджреНрд╡рд╛рд░рд╛ рдЙрдкрдпреЛрдЧ рдХреЗ рд▓рд┐рдП рдЙрдЪрд┐рдд рдХрдиреЗрдХреНрд╢рди рд╕реЗрдЯрд┐рдВрдЧреНрд╕ рд╕реЗрдЯ рдХреАрдВ, рдмрд╕ рдЬреЛ рд╣рдордиреЗ рд╕реНрдЯрд╛рд░реНрдЯрд░ рдСрдЯреЛ-рдХреЙрдиреНрдлрд╝рд┐рдЧрд░реЗрд╢рди рдореЗрдВ рд╡рд░реНрдгрд┐рдд рдХреА рд╣реИрдВ:


 System.setProperty("clusterj.connectString", ndbUrl); System.setProperty("clusterj.dataBaseName", CLUSTERJ_DATABASE); System.setProperty("spring.datasource.username", MYSQL_USER); System.setProperty("spring.datasource.password", MYSQL_PASSWORD); System.setProperty("spring.datasource.url", mysqlConnectionString); 

рдЕрдЧрд▓рд╛, рд╣рдо рдПрдХ рдПрдиреЛрдЯреЗрд╢рди рд▓рд┐рдЦрддреЗ рд╣реИрдВ рдЬреЛ рд╣рдореЗрдВ рдкрд░реАрдХреНрд╖рдгреЛрдВ рдореЗрдВ рдХрдВрдЯреЗрдирд░реЛрдВ рдХреЛ рдШреЛрд╖рд┐рдд рд░реВрдк рд╕реЗ рдмрдврд╝рд╛рдиреЗ рдХреА рдЕрдиреБрдорддрд┐ рджреЗрдЧрд╛ред рдпрд╣рд╛рдВ рд╕рдм рдХреБрдЫ рдмрд╣реБрдд рд╕рд░рд▓ рд╣реИ, рд╣рдо рдЕрдкрдиреЗ рдПрдХреНрд╕рдЯреЗрдВрд╢рди рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рддреЗ рд╣реИрдВ:


 @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @ExtendWith(MySQLClusterTcExtension.class) public @interface EnableMySQLClusterContainer { } 

рдЕрдВрдд рдореЗрдВ, рд╣рдо рдкрд░реАрдХреНрд╖рдг рд▓рд┐рдЦрддреЗ рд╣реИрдВ:


 @Test void shouldGetUserViaClusterJ() { User newUser = session.newInstance(User.class); newUser.setId(1); newUser.setFirstName("John"); newUser.setLastName("Jonson"); session.persist(newUser); User userFromDb = session.find(User.class, 1); assertAll( () -> assertEquals(userFromDb.getId(), 1), () -> assertEquals(userFromDb.getFirstName(), "John"), () -> assertEquals(userFromDb.getLastName(), "Jonson")); } 

рдпрд╣ рдкрд░реАрдХреНрд╖рдг рджрд┐рдЦрд╛рддрд╛ рд╣реИ рдХрд┐ рд╣рдо рдкреНрд░рд╛рдердорд┐рдХ рдХреБрдВрдЬреА рджреНрд╡рд╛рд░рд╛ рд░рд┐рдХреЙрд░реНрдб рдХреИрд╕реЗ рдкреНрд░рд╛рдкреНрдд рдХрд░ рд╕рдХрддреЗ рд╣реИрдВред рдпрд╣ рдХреНрд╡реЗрд░реА SQL рдХреНрд╡реЗрд░реА рдХреЗ рдмрд░рд╛рдмрд░ рд╣реИ:


 SELECT * FROM user WHERE id = 1; 

рдЪрд▓реЛ рдПрдХ рдФрд░ рдкрд░реАрдХреНрд╖рдг рдХрд░рддреЗ рд╣реИрдВ, рдЕрдзрд┐рдХ рдЬрдЯрд┐рд▓ рддрд░реНрдХ рдХреЗ рд╕рд╛рде:


 @Test void queryBuilderTest() { QueryBuilder builder = session.getQueryBuilder(); QueryDomainType<User> userQueryDomainType = builder.createQueryDefinition(User.class); // parameter PredicateOperand propertyIdParam = userQueryDomainType.param("lastName"); // property PredicateOperand propertyEntityId = userQueryDomainType.get("lastName"); userQueryDomainType.where(propertyEntityId.equal(propertyIdParam)); Query<User> query = session.createQuery(userQueryDomainType); query.setParameter("lastName", "Jonson"); List<User> foundEntities = query.getResultList(); Optional<User> firstUser = foundEntities.stream().filter(u -> u.getId() == 1).findFirst(); Optional<User> secondUser = foundEntities.stream().filter(u -> u.getId() == 2).findFirst(); assertAll( () -> assertEquals(foundEntities.size(), 2), () -> assertTrue(firstUser.isPresent()), () -> assertTrue(secondUser.isPresent()), () -> assertThat(firstUser.get(), allOf( hasProperty("firstName", equalTo("John")), hasProperty("lastName", equalTo("Jonson")) ) ), () -> assertThat(secondUser.get(), allOf( hasProperty("firstName", equalTo("Alex")), hasProperty("lastName", equalTo("Jonson")) ) ) ); } 

QueryBuilder рдЙрдкрдпреЛрдЧ рдЬрдЯрд┐рд▓ рдкреНрд░рд╢реНрдиреЛрдВ рдХрд╛ рдирд┐рд░реНрдорд╛рдг рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИ, where , equal , like QueryBuilder ред рдЗрд╕ рдкрд░реАрдХреНрд╖рдг рдореЗрдВ, рд╣рдо рдЙрди рд╕рднреА рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛рдУрдВ рдХреЛ рдмрд╛рд╣рд░ рдирд┐рдХрд╛рд▓рддреЗ рд╣реИрдВ рдЬрд┐рдирдХрд╛ рдЕрдВрддрд┐рдо рдирд╛рдо = рдЬреЛрдВрд╕рди рд╣реИред рдпрд╣ рдХреНрд╡реЗрд░реА рдирд┐рдореНрди SQL рдмрд░рд╛рдмрд░ рд╣реИ:


 SELECT * FROM user WHERE lastName = 'Jonson'; 

рдпрд╣рд╛рдБ рднреА, рдПрдХ рд╕рдорд╕реНрдпрд╛ рдореЗрдВ рднрд╛рдЧ рдЧрдпрд╛ред рдкреНрд░рдкрддреНрд░ рдХреА рдПрдХ рдХреНрд╡реЗрд░реА рд╕рдВрдХрд▓рд┐рдд рдХрд░рдиреЗ рдореЗрдВ рдЕрд╕рдорд░реНрде


 SELECT * FROM user WHERE (lastName = 'Jonson' and firstName = 'John') or id = 2; 

рдпрд╣ рд╕реБрд╡рд┐рдзрд╛ рд╡рд░реНрддрдорд╛рди рдореЗрдВ рд▓рд╛рдЧреВ рдирд╣реАрдВ рд╣реИред рдЖрдк рдкрд░реАрдХреНрд╖рдг рджреЗрдЦ рд╕рдХрддреЗ рд╣реИрдВ: andOrNotImplemented ред


рдкреВрд░реНрдг рдкрд░реАрдХреНрд╖рдг рдЙрджрд╛рд╣рд░рдг
 @SpringBootTest @ExtendWith(SpringExtension.class) @EnableAutoConfiguration @EnableMySQLClusterContainer class NdbClusterJTest { @Autowired private JdbcTemplate jdbcTemplate; @Autowired private SessionFactory sessionFactory; private Session session; @BeforeEach void setUp() { jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS `user` (id INT NOT NULL PRIMARY KEY," + " firstName VARCHAR(64) DEFAULT NULL," + " lastName VARCHAR(64) DEFAULT NULL) ENGINE=NDBCLUSTER;"); session = sessionFactory.getSession(); } @Test void shouldGetUserViaClusterJ() { User newUser = session.newInstance(User.class); newUser.setId(1); newUser.setFirstName("John"); newUser.setLastName("Jonson"); session.persist(newUser); User userFromDb = session.find(User.class, 1); assertAll( () -> assertEquals(userFromDb.getId(), 1), () -> assertEquals(userFromDb.getFirstName(), "John"), () -> assertEquals(userFromDb.getLastName(), "Jonson")); } @Test void queryBuilderTest() { User newUser1 = session.newInstance(User.class); newUser1.setId(1); newUser1.setFirstName("John"); newUser1.setLastName("Jonson"); User newUser2 = session.newInstance(User.class); newUser2.setId(2); newUser2.setFirstName("Alex"); newUser2.setLastName("Jonson"); session.persist(newUser1); session.persist(newUser2); QueryBuilder builder = session.getQueryBuilder(); QueryDomainType<User> userQueryDomainType = builder.createQueryDefinition(User.class); // parameter PredicateOperand propertyIdParam = userQueryDomainType.param("lastName"); // property PredicateOperand propertyEntityId = userQueryDomainType.get("lastName"); userQueryDomainType.where(propertyEntityId.equal(propertyIdParam)); Query<User> query = session.createQuery(userQueryDomainType); query.setParameter("lastName", "Jonson"); List<User> foundEntities = query.getResultList(); Optional<User> firstUser = foundEntities.stream().filter(u -> u.getId() == 1).findFirst(); Optional<User> secondUser = foundEntities.stream().filter(u -> u.getId() == 2).findFirst(); assertAll( () -> assertEquals(foundEntities.size(), 2), () -> assertTrue(firstUser.isPresent()), () -> assertTrue(secondUser.isPresent()), () -> assertThat(firstUser.get(), allOf( hasProperty("firstName", equalTo("John")), hasProperty("lastName", equalTo("Jonson")) ) ), () -> assertThat(secondUser.get(), allOf( hasProperty("firstName", equalTo("Alex")), hasProperty("lastName", equalTo("Jonson")) ) ) ); } @Test void andOrNotImplemented() { QueryBuilder builder = session.getQueryBuilder(); QueryDomainType<User> userQueryDomainType = builder.createQueryDefinition(User.class); // parameter PredicateOperand firstNameParam = userQueryDomainType.param("firstName"); // property PredicateOperand firstName = userQueryDomainType.get("firstName"); // parameter PredicateOperand lastNameParam = userQueryDomainType.param("lastName"); // property PredicateOperand lastName = userQueryDomainType.get("lastName"); // parameter PredicateOperand idParam = userQueryDomainType.param("id"); // property PredicateOperand id = userQueryDomainType.get("id"); Executable executable = () -> userQueryDomainType.where(firstNameParam.equal(firstName) .and(lastNameParam.equal(lastName)) .or(idParam.equal(id))); UnsupportedOperationException exception = assertThrows(UnsupportedOperationException.class, executable); assertEquals("Not implemented.", exception.getMessage()); } @AfterEach void tearDown() { session.deletePersistentAll(User.class); session.close(); } } 

рд╣рдорд╛рд░реЗ рдПрдиреЛрдЯреЗрд╢рди @EnableMySQLClusterContainer рдХреЗ рд▓рд┐рдП рдзрдиреНрдпрд╡рд╛рдж, рд╣рдордиреЗ рдкрд░реАрдХреНрд╖рдгреЛрдВ рдХреЗ рд▓рд┐рдП рд╡рд╛рддрд╛рд╡рд░рдг рддреИрдпрд╛рд░ рдХрд░рдиреЗ рдХреЗ рд╡рд┐рд╡рд░рдг рдХреЛ рдЫрд┐рдкрд╛ рджрд┐рдпрд╛ред рдЗрд╕рдХреЗ рдЕрд▓рд╛рд╡рд╛, рд╣рдорд╛рд░реЗ рд╕реНрдЯрд╛рд░реНрдЯрд░ рдХреЗ рд▓рд┐рдП, рд╣рдо рдмрд╕ рдЕрдкрдиреЗ рдкрд░реАрдХреНрд╖рдг рдореЗрдВ рд╕реЗрд╢рдирдлреИрдХреНрдЯрд░реА рдХреЛ рдЗрдВрдЬреЗрдХреНрдЯ рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ, рдФрд░ рдЗрд╕реЗ рдЕрдкрдиреА рдЖрд╡рд╢реНрдпрдХрддрд╛рдУрдВ рдХреЗ рд▓рд┐рдП рдЙрдкрдпреЛрдЧ рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ, рдЗрд╕ рддрдереНрдп рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рдЪрд┐рдВрддрд╛ рдХрд┐рдП рдмрд┐рдирд╛ рдХрд┐ рдЗрд╕реЗ рдореИрдиреНрдпреБрдЕрд▓ рд░реВрдк рд╕реЗ рдмрдирд╛рдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИред
рдпрд╣ рд╕рдм рд╣рдореЗрдВ рд╕реЗрд╡рд╛ рдХреЗ рдмреБрдирд┐рдпрд╛рджреА рдврд╛рдВрдЪреЗ рдХреЗ рдмрдЬрд╛рдп рдкрд░реАрдХреНрд╖рдгреЛрдВ рдХреЗ рд╡реНрдпрд╛рдкрд╛рд░ рддрд░реНрдХ рдХреЛ рд▓рд┐рдЦрдиреЗ рдкрд░ рдХреЗрдВрджреНрд░рд┐рдд рдХрд░рддрд╛ рд╣реИред


рдореИрдВ рдЗрд╕ рддрдереНрдп рдкрд░ рднреА рдзреНрдпрд╛рди рджреЗрдирд╛ рдЪрд╛рд╣рддрд╛ рд╣реВрдВ рдХрд┐ рдЖрдкрдХреЛ рдкреИрд░рд╛рдореАрдЯрд░ рдХреЗ рд╕рд╛рде ClusterJ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдиреЗ рд╡рд╛рд▓реЗ рдПрдкреНрд▓рд┐рдХреЗрд╢рди рдХреЛ рдЪрд▓рд╛рдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИ:


 -Djava.library.path=/usr/lib/x86_64-linux-gnu/ 

рдЬреЛ libndbclient.so рдХреЛ рд░рд╛рд╕реНрддрд╛ libndbclient.so ред рдЗрд╕рдХреЗ рдмрд┐рдирд╛, рдХреБрдЫ рднреА рдХрд╛рдо рдирд╣реАрдВ рдХрд░реЗрдЧрд╛ред


рдирд┐рд╖реНрдХрд░реНрд╖


рдореЗрд░реЗ рд▓рд┐рдП, ClusterJ рдЙрди рдкреНрд░рдгрд╛рд▓рд┐рдпреЛрдВ рдореЗрдВ ClusterJ рдЕрдЪреНрдЫреА рдмрд╛рдд рд╣реИ рдЬреЛ рдбреЗрдЯрд╛ рдПрдХреНрд╕реЗрд╕ рд╕реНрдкреАрдб рдХреЗ рд▓рд┐рдП рдорд╣рддреНрд╡рдкреВрд░реНрдг рд╣реИрдВ, рд▓реЗрдХрд┐рди рдорд╛рдореВрд▓реА рдЦрд╛рдорд┐рдпрд╛рдВ рдФрд░ рд╕реАрдорд╛рдПрдВ рд╕рдордЧреНрд░ рдкреНрд░рднрд╛рд╡ рдХреЛ рдЦрд░рд╛рдм рдХрд░рддреА рд╣реИрдВред рдпрджрд┐ рдЖрдкрдХреЗ рдкрд╛рд╕ рдЪреБрдирдиреЗ рдХрд╛ рдЕрд╡рд╕рд░ рд╣реИ рдФрд░ рдЖрдк рдкрд╣реБрдВрдЪ рдХреА рдЧрддрд┐ рдХреА рдкрд░рд╡рд╛рд╣ рдирд╣реАрдВ рдХрд░рддреЗ рд╣реИрдВ, рддреЛ рдореБрдЭреЗ рд▓рдЧрддрд╛ рд╣реИ рдХрд┐ JDBC рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдирд╛ рдмреЗрд╣рддрд░ рд╣реИред


рд▓реЗрдЦ рдиреЗ рд▓реЗрдирджреЗрди рдФрд░ рддрд╛рд▓реЗ рдХреЗ рд╕рд╛рде рдХрд╛рдо рдХрд░рдиреЗ рдкрд░ рд╡рд┐рдЪрд╛рд░ рдирд╣реАрдВ рдХрд┐рдпрд╛, рдФрд░ рдЗрд╕рд▓рд┐рдП рдпрд╣ рдХрд╛рдлреА рдмрджрд▓ рдЧрдпрд╛ред


рдпрд╣ рдмрд╛рдд рд╣реИ, рд╣реИрдкреНрдкреА рдХреЛрдбрд┐рдВрдЧ!


рдЙрдкрдпреЛрдЧреА рд▓рд┐рдВрдХ:


рдкрд░рд┐рдпреЛрдЬрдирд╛ рдХреЗ рд╕рд╛рде рд╕рднреА рдХреЛрдб рдпрд╣рд╛рдБ рдирд┐рд╣рд┐рдд рд╣реИрдВ
рдбрд╛рдЙрдирд▓реЛрдб рдкреГрд╖реНрда
ClusterJ рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рдЬрд╛рдирдХрд╛рд░реА
рдЬрд╛рд╡рд╛ рдФрд░ рдПрдирдбреАрдмреА рдХреНрд▓рд╕реНрдЯрд░ рдХреЗ рд╕рд╛рде рдХрд╛рдо рдХрд░реЗрдВ
рдкреНрд░реЛ MySQL NDB рдХреНрд▓рд╕реНрдЯрд░ рдмреБрдХ
MySQL NDB рдХреНрд▓рд╕реНрдЯрд░ рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рдФрд░ рдпрд╣рд╛рдБ


MySQL рд░рд┐рдкреЙрдЬрд┐рдЯрд░реА рдореЗрдВ рдФрд░ рднреА рдЕрдзрд┐рдХ рдкрд░реАрдХреНрд╖рдг рдорд╛рдорд▓реЗред

Source: https://habr.com/ru/post/hi472468/


All Articles