There are two entities: parent and child one.
#Entity
#Table(name = "university_group")
public class Group {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private long id;
private String name;
#OneToMany(mappedBy = "group", fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
private Set<Student> students = new HashSet<>();
// getters, setters, constructor, equals+hashcode ...
}
#Entity
#Table(name = "student")
public class Student {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private long id;
private String name;
private String password;
private String email;
#ManyToOne(optional = false)
private Group group;
// getters, setters, constructor, equals+hashcode ...
}
After removing group by em.remove(group) an exception is thrown:
javax.persistence.PersistenceException:
org.hibernate.exception.ConstraintViolationException ...
org.hibernate.engine.jdbc.spi.SqlExceptionHelper: ERROR:
UPDATE or DELETE in table "university_group" breaks foreign key constraint "fk_20su8ubuwt33je1a3ygal7wd6" of table "student"
It seems like hibernate is not deleting students before the group by means of Persistence Provider although it should. Of course, I am able to enable DB cascading, but I would better solve the problem.
Any ideas?
Configured the EntityManager by Spring configs
#Configuration
#EnableTransactionManagement
#PropertySource({"classpath:db.properties"})
public class PersistenceContext {
private static final String BASE_MODEL_SCAN_PACKAGE = "com.chiefhelpsystem.model";
#Value("${db.driverClassName}")
private String dbClassName;
#Value("${db.url}")
private String dbUrl;
#Value("${db.username}")
private String dbUserName;
#Value("${db.password}")
private String dbPassword;
#Bean
DataSource dataSource() {
BasicDataSource ds = new BasicDataSource();
ds.setMaxIdle(20);
ds.setMinIdle(0);
ds.setMaxActive(20);
ds.setDriverClassName(dbClassName);
ds.setUrl(dbUrl);
ds.setUsername(dbUserName);
ds.setPassword(dbPassword);
return ds;
}
#Bean
PlatformTransactionManager transactionManager() {
return new JpaTransactionManager();
}
#Bean(destroyMethod = "destroy")
LocalContainerEntityManagerFactoryBean emf() {
LocalContainerEntityManagerFactoryBean emFactory =
new LocalContainerEntityManagerFactoryBean();
HibernateJpaVendorAdapter jpaVendorAdapter = new HibernateJpaVendorAdapter();
jpaVendorAdapter.setDatabase(Database.POSTGRESQL);
jpaVendorAdapter.setGenerateDdl(true);
jpaVendorAdapter.setShowSql(true);
emFactory.setDataSource(dataSource());
emFactory.setPackagesToScan(BASE_MODEL_SCAN_PACKAGE);
emFactory.setJpaVendorAdapter(jpaVendorAdapter);
emFactory.setJpaProperties(jpaProps());
emFactory.setPersistenceProvider(new HibernatePersistenceProvider());
return emFactory;
}
private Properties jpaProps() {
Properties properties = new Properties();
properties.setProperty("format_sql", "true");
return properties;
}
}
Hibernate 4.3.11, Spring 4.3.2
The problem was in the incorrect hachcode() method realization. As soon as I deleted it from sources, the "un-managed deleting" problem has appeared in hibernate TRACE logs and it should be further fixed.
Related
I am using Springboot for an app that uses some classes that are managed in another application and stored in another database. My Flight class, which is managed in the current application in a local database, has an attribute of type Aircraft, which is also defined in the current application but is managed in the other application and stored in another database.
The Flight class:
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
#Entity
#Table(schema = "app1")
public class Flight implements Serializable {
#Id
#GeneratedValue(
strategy = GenerationType.SEQUENCE,
generator = "flight_sequence"
)
#SequenceGenerator(
name = "flight_sequence",
allocationSize = 1
)
#Column(nullable = false, updatable = false)
private Long id;
private String callsign;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="aircraft_id", nullable=false)
private Aircraft aircraft;
private Date date;
private Operator operator;
private String origin;
private String destination;
}
My Aircraft class:
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#Entity
#Table(schema = "app2")
public class Aircraft implements Serializable {
#Id
#GeneratedValue(
strategy = GenerationType.SEQUENCE,
generator = "aircraft_sequence"
)
#SequenceGenerator(
name = "aircraft_sequence",
allocationSize = 1
)
#Column(nullable = false, updatable = false)
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="operator_id", nullable=false)
private Operator operator;
private String registration;
private String acType;
}
I wrote a Jpa query in a FlightRepository which uses as a parameter the registration attribute from the Aircraft attribute of the Flight class:
public interface FlightRepository extends JpaRepository<Flight, Long> {
Flight findFirstByDestinationAndAircraftRegistrationOrderByDateDesc(String destination, String registration);
}
but this results in the following exception:
Caused by: org.hibernate.AnnotationException: #OneToOne or #ManyToOne on com.student.application.domain.app1.Flight.aircraft references an unknown entity: com.student.application.domain.app2.Aircraft
at org.hibernate.cfg.ToOneFkSecondPass.doSecondPass(ToOneFkSecondPass.java:100)
at org.hibernate.boot.internal.InFlightMetadataCollectorImpl.processEndOfQueue(InFlightMetadataCollectorImpl.java:1750)
at org.hibernate.boot.internal.InFlightMetadataCollectorImpl.processFkSecondPassesInOrder(InFlightMetadataCollectorImpl.java:1694)
at org.hibernate.boot.internal.InFlightMetadataCollectorImpl.processSecondPasses(InFlightMetadataCollectorImpl.java:1623)
at org.hibernate.boot.model.process.spi.MetadataBuildingProcess.complete(MetadataBuildingProcess.java:295)
at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.metadata(EntityManagerFactoryBuilderImpl.java:1460)
at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:1494)
at org.springframework.orm.jpa.vendor.SpringHibernateJpaPersistenceProvider.createContainerEntityManagerFactory(SpringHibernateJpaPersistenceProvider.java:58)
at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.createNativeEntityManagerFactory(LocalContainerEntityManagerFactoryBean.java:365)
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.buildNativeEntityManagerFactory(AbstractEntityManagerFactoryBean.java:409)
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.afterPropertiesSet(AbstractEntityManagerFactoryBean.java:396)
at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.afterPropertiesSet(LocalContainerEntityManagerFactoryBean.java:341)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1863)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1800)
... 86 more
If I add a #OneToMany annotation to the Aircraft class the problem remains the same. If I remove both the #OneToMany and #ManyToOne annotations, I get a Cannot join to attribute of basic type exception.
These are the configuration classes for the two databases:
#Configuration
#PropertySource({"classpath:application.properties"})
#EnableJpaRepositories(
basePackages = "com.student.application.repository.app1",
entityManagerFactoryRef = "app1EntityManager",
transactionManagerRef = "app1TransactionManager")
public class App1DBConfiguration {
#Autowired
private Environment env;
#Primary
#Bean
#ConfigurationProperties(prefix = "spring.datasource")
public DataSource app1DataSource() {
return DataSourceBuilder.create().build();
}
#Bean
#Primary
public LocalContainerEntityManagerFactoryBean app1EntityManager() {
LocalContainerEntityManagerFactoryBean em
= new LocalContainerEntityManagerFactoryBean();
em.setDataSource(app1DataSource());
em.setPackagesToScan(
"com.student.application.domain.app1");
HibernateJpaVendorAdapter vendorAdapter
= new HibernateJpaVendorAdapter();
em.setJpaVendorAdapter(vendorAdapter);
HashMap<String, Object> properties = new HashMap<>();
properties.put("hibernate.hbm2ddl.auto",
env.getProperty("spring.jpa.hibernate.ddl-auto"));
properties.put("hibernate.dialect",
env.getProperty("spring.jpa.properties.hibernate.dialect"));
properties.put("hibernate.dialect.storage_engine",
env.getProperty("spring.jpa.properties.hibernate.dialect.storage_engine"));
em.setJpaPropertyMap(properties);
return em;
}
#Primary
#Bean
public PlatformTransactionManager app1TransactionManager() {
JpaTransactionManager transactionManager
= new JpaTransactionManager();
transactionManager.setEntityManagerFactory(
app1EntityManager().getObject());
return transactionManager;
}
}
#Configuration
#PropertySource({"classpath:application.properties"})
#EnableJpaRepositories(
basePackages = "com.student.application.repository.app2",
entityManagerFactoryRef = "app2EntityManager",
transactionManagerRef = "app2TransactionManager")
public class App2DBConfiguration {
#Autowired
private Environment env;
#Bean
#ConfigurationProperties(prefix = "spring.app2")
public DataSource app2DataSource() {
return DataSourceBuilder.create().build();
}
#Bean
public LocalContainerEntityManagerFactoryBean app2EntityManager() {
LocalContainerEntityManagerFactoryBean em
= new LocalContainerEntityManagerFactoryBean();
em.setDataSource(app2DataSource());
em.setPackagesToScan(
"com.student.application.domain.app2");
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
em.setJpaVendorAdapter(vendorAdapter);
HashMap<String, Object> properties = new HashMap<>();
properties.put("hibernate.hbm2ddl.auto",
"none");
properties.put("hibernate.implicit_naming_strategy", "org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy");
properties.put("hibernate.physical_naming_strategy", "org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy");
em.setJpaPropertyMap(properties);
return em;
}
#Bean
public PlatformTransactionManager app2TransactionManager() {
JpaTransactionManager transactionManager
= new JpaTransactionManager();
transactionManager.setEntityManagerFactory(
app2EntityManager().getObject());
return transactionManager;
}
}
One flight can have just one Aircraft but one aircraft can have more than one flight... so on aircraft you should have
#OneToMany(mappedBy = "Flight") Join column is not needed here
Im assuming you want it to be mapped by flight and have all the info stored in flight table. you could allso join tables and make flight_aircraft table.
annotations On Flight ->
#ManyToOne
#JoinColumn(name="aircraft_id")
My table(tbl_branch_type) exists in my database. Every other column is there, but I receive this error:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [com/example/local/config/LocalDbConfiguration.class]: Invocation of init method failed; nested exception is org.hibernate.AnnotationException: Unable to create unique key constraint (code, bank_type_id) on table tbl_branch_type: database column 'bank_type_id' not found. Make sure that you use the correct column name which depends on the naming strategy in use (it may not be the same as the property name in the entity, especially for relational types)
My BranchType entity is:
#Entity
#Table(
name = "tbl_branch_type",
uniqueConstraints = {
#UniqueConstraint(
name = "uc_branch_type_bank_id_branch_code",
columnNames = {"code", "bank_type_id"})
})
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
#Getter
#Setter
#EqualsAndHashCode(
callSuper = true,
exclude = {"bankType"})
#ToString(exclude = {"bankType"})
public class BranchType extends Auditing implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "seq_branch_type")
#SequenceGenerator(sequenceName = "seq_branch_type", allocationSize = 1, name = "seq_branch_type")
private Long id;
#NotNull
#Size(min = 1, max = 20)
#Column(name = "code", length = 20, nullable = false)
private String code;
#NotNull
#Size(min = 1, max = 100)
#Column(name = "name", length = 100, nullable = false)
private String name;
#JsonIgnore
#ManyToOne
#JsonIgnoreProperties("")
private BankType bankType;
}
My LocalDbConfiguration class is:
#Configuration
#PropertySource({"classpath:application.yml"})
#EnableJpaRepositories(
basePackages = {"com.example.local.model.dao", "com.example.local.core.auth.repository"})
#EnableJpaAuditing(auditorAwareRef = "auditorProvider")
public class LocalDbConfiguration {
#Primary
#Bean(name = "dataSource")
#ConfigurationProperties(prefix = "spring.datasource")
public DataSource userDataSource() {
return DataSourceBuilder.create().build();
}
#Primary
#Bean(name = "entityManagerFactory")
public LocalContainerEntityManagerFactoryBean entityManagerFactory(
EntityManagerFactoryBuilder builder, #Qualifier("dataSource") DataSource dataSource) {
Map<String, Object> properties = new HashMap<>();
properties.put("hibernate.hbm2ddl.auto", "update");
properties.put("database.platform", "org.hibernate.dialect.Oracle10gDialect");
return builder
.dataSource(dataSource)
.packages(
"com.example.local.model.entity",
"com.example.local.model.mapper",
"com.example.local.core.auth.domain")
.persistenceUnit("localPU")
.properties(properties)
.build();
}
#Primary
#Bean(name = "transactionManager")
public PlatformTransactionManager transactionManager(
#Qualifier("entityManagerFactory") EntityManagerFactory entityManagerFactory) {
return new JpaTransactionManager(entityManagerFactory);
}
#Bean
#Primary
#ConfigurationProperties("spring.datasource.hikari")
public HikariConfig defaultHikariConfig() {
return new HikariConfig();
}
#Bean
AuditorAware<Long> auditorProvider() {
return new AuditorProviderAware();
}
}
I believe this is the root cause of your problem: database column 'bank_type_id' not found. Try to create that column
I have solved my problem by adding this codes for entityManagerFactory in LocalDbConfiguration.class
properties.put(
"hibernate.physical_naming_strategy",
"org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy");
properties.put(
"hibernate.implicit_naming_strategy",
"org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy");
The same question has been asked number of times but none of them has solution to my problem.
I have created a hibernate + H2 + Sprinvg mvc project. I am using java based configuration. I have the following beans related to Datasource, SessionFactory and TransactionManager
#Configuration
#ComponentScan(basePackages="org.testpackage")
#EnableWebMvc
#EnableTransactionManagement
public class MyConfiguration extends WebMvcConfigurationSupport {
#Bean(initMethod="start",destroyMethod="stop")
public org.h2.tools.Server h2WebConsonleServer () throws SQLException {
return org.h2.tools.Server.createWebServer("-web","-webAllowOthers","-
webDaemon","-webPort", "8082");
}
#Bean
public DataSource getDataSource() {
return new EmbeddedDatabaseBuilder()
.generateUniqueName(false)
.setName("mytestdb")
.setType(EmbeddedDatabaseType.H2)
.addDefaultScripts()
.setScriptEncoding("UTF-8")
.ignoreFailedDrops(true)
.build();
}
#Bean
public LocalSessionFactoryBean sessionFactory() {
final LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
sessionFactory.setDataSource(getDataSource());
sessionFactory.setHibernateProperties(hibernateProperties());
sessionFactory.setPackagesToScan(new String[] {"org.testpackage.model"});
return sessionFactory;
}
#Bean
#Autowired
public HibernateTransactionManager transactionManager(final SessionFactory sessionFactory) {
final HibernateTransactionManager txManager = new HibernateTransactionManager();
txManager.setSessionFactory(sessionFactory);
return txManager;
}
final Properties hibernateProperties() {
final Properties hibernateProperties = new Properties();
hibernateProperties.setProperty("hibernate.hbm2ddl.auto", "validate");
hibernateProperties.setProperty("hibernate.show_sql", "true");
return hibernateProperties;
}
//Some more beans
}
I have the following Entity class
#Entity
#Table(name = "MYTESTDB.TEST_TABLE")
public class User{
#Id
#GeneratedValue
#Column(name = "id")
private int id;
#Column(name = "name")
private String name;
#Column(name = "email", unique = true)
private String email;
public User(int id, String name, String email) {
super();
this.id = id;
this.name = name;
this.email = email;
}
public User() {
}
//Getters and Setters
}
in the DataSource bean I am using addDefaultScripts() and I have 2 sql scripts which create Schema in H2 and insert some predefined value. Which are as follows.
//schema.sql Script
CREATE SCHEMA `MYTESTDB` ;
Drop TABLE IF EXISTS MYTESTDBDB.TEST_TABLE;
CREATE TABLE MYTESTDB.TEST_TABLE (
ID INT NOT NULL PRIMARY KEY,
NAME VARCHAR(50) NOT NULL,
EMAIL VARCHAR(20) NOT NULL,
);
CREATE UNIQUE INDEX ON MYTESTDB.TEST_TABLE (EMAIL)
//data.sql Script
INSERT INTO MYTESTDB.TEST_TABLE(id, name, email)
VALUES ('1', 'Tom', 'tom12#hotmail.com');
If I use hibernate hbm2ddl.auto property value "create" everything works fine, hibernate drops the table and recreate it. I have verified it from the web browser. But if I use "validate" property I get the following error exception
Error creating bean with name 'sessionFactory' defined in org.testPackage.configuration.MYConfiguration: Invocation of init method failed; nested exception is org.hibernate.tool.schema.spi.SchemaManagementException: Schema-validation: missing table [MYTESTDB.TEST_TABLE]
Can anyone please help me to find out the problem?
Its working now. With the help of #Slaw I am able to use "Validate" property.
Use #Table(schema = "MYTESTDB", name = "TEST_TABLE") instead of #Table(name = "MYTESTDB.TEST_TABLE"). But I had to change a bit more in user entity class. Instead of #GeneratedValue annotation I added #GeneratedValue(strategy = GenerationType.IDENTITY). Now everything is working fine.
Thanks #Slaw and #Mykhailo for your valuable time.
I have 2 entities - User and Role with mapping #ManyToOne. I want to change role in user, but Role want's to be updated too.
User entity:
#ManyToOne
#JoinColumn(name = "role_id", insertable = false, updatable = false)
private Role role;
role entity:
#OneToMany(mappedBy = "role")
private Set<User> users;
The error I get is:
java.lang.IllegalStateException: org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance beforeQuery flushing: com.spring.model.Role
at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:144)
at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:155)
at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:162)
at org.hibernate.internal.SessionImpl.doFlush(SessionImpl.java:1434)
My tables in DB are not set with CASCADE UPDATE or INSERT. I wasn't able to find appropriate solution. Thanks for your help
EDIT:
This is how I update User
public void update(User user) {
User entity = dao.findById(user.getId());
if(entity!=null) {
entity.setRole(user.getRole());
}
}
EDIT2:
My hibernate configuration
#Configuration
#EnableTransactionManagement
#ComponentScan({ "com.spring.configuration" })
#PropertySource(value = { "classpath:application.properties" })
public class HibernateConfiguration {
#Autowired
private Environment environment;
#Bean
public LocalSessionFactoryBean sessionFactory() {
LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
sessionFactory.setDataSource(dataSource());
sessionFactory.setPackagesToScan(new String[] { "com.spring.model" });
sessionFactory.setHibernateProperties(hibernateProperties());
return sessionFactory;
}
#Bean
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(environment.getRequiredProperty("jdbc.driverClassName"));
dataSource.setUrl(environment.getRequiredProperty("jdbc.url"));
dataSource.setUsername(environment.getRequiredProperty("jdbc.username"));
dataSource.setPassword(environment.getRequiredProperty("jdbc.password"));
return dataSource;
}
private Properties hibernateProperties() {
Properties properties = new Properties();
properties.put("hibernate.dialect", environment.getRequiredProperty("hibernate.dialect"));
properties.put("hibernate.show_sql", environment.getRequiredProperty("hibernate.show_sql"));
properties.put("hibernate.format_sql", environment.getRequiredProperty("hibernate.format_sql"));
return properties;
}
#Bean
#Autowired
public HibernateTransactionManager transactionManager(SessionFactory s) {
HibernateTransactionManager txManager = new HibernateTransactionManager();
txManager.setSessionFactory(s);
return txManager;
}
}
You should use the cascade attribute of the OneToMany annotation, it does not have any relationship with the database cascade operations (but can be affected by that).
#OneToMany(cascade = CascadeType.ALL, mappedBy="role", orphanRemoval = true)
private Set<User> users;
This way the operation will be propagated to the collection elements. You can check this answer for more info.
You can also modify the cascade property of the ManyToOne annotation.
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "role_id")
private Role role;
I'm using the mentioned libraries and want to persist a simple object, that has a one-to-one-relationship with another, without persisting the associated object. That might sound simple, because it should be the default behaviour. However, in my case for some reason it seems not to be.
I'm using an entityManager configured by this code:
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource, Environment env) {
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setDatabase(Database.POSTGRESQL);
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setJpaVendorAdapter(vendorAdapter);
factory.setDataSource(dataSource);
return factory;
}
#Bean
public JpaTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactory);
return transactionManager;
}
Here are my entity classes:
#Entity
#Table(name = "`Table_A`", schema = "public")
public class ClassA {
#Id
#Column(name = "a_id")
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name = "value")
private String value;
#Column(name = "time")
private LocalDateTime time;
#OneToOne(targetEntity = ClassB.class, fetch = FetchType.EAGER)
#JoinColumn(nullable = false, name = "b_id_ref")
private ClassB b;
// getters and setters
}
#Entity
#Table(name = "`Table_B`", schema = "public")
public class ClassB {
#Id
#Column(name = "b_id")
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
#Column(name = "value")
private String value;
// getters and setters
}
I don't want the object that b in ClassA references to be persisted. But if I change value in an instance of ClassA and also value in the corresponding instance of ClassB. I wrote a small test to demonstrate the program flow:
#Autowired
private ClassARepository repository;
#PersistenceContext
private EntityManager em;
#Test
public void testCascading() {
Session hibernateSession = em.unwrap(Session.class);
String newValueA = "Sissi";
String newValueB = "Franzl";
ClassA a = createTestInstance();
ClassB b = a.getB();
String oldValueA = token.getTokenValue();
String oldValueB = user.getFirstname();
a.setValue(newValueA);
b.setValue(newValueB);
repository.save(a);
em.flush();
hibernateSession.evict(a);
hibernateSession.evict(b);
ClassA loadedA = repository.findById(1L);
assertThat(loadedA.getB().getValue(), equalTo(oldValueB));
assertThat(loadedA.getValue(), equalTo(newValueA));
}
I checked that the bahaviour is the same in the "real world". Like it is stated here the save-operation should not be cascaded. I must miss something here. Is there maybe a global setting that changes the default behaviour? What can I do to achieve the desired behaviour?
Your test is transactional. So you're getting a managed B, and changing the value of one of its field. And the change is thus made persistent.
Your calls to save() and flush() are useless, by the way. The whole point of JPA is that when entities are managed, their state is made persistent automatically. It's a feature, not a bug.